Skip to content

ci: run viewer/tests in CI (close the viewer-coverage gap) - #403

Merged
100yenadmin merged 1 commit into
mainfrom
ci/viewer-tests-job
May 30, 2026
Merged

ci: run viewer/tests in CI (close the viewer-coverage gap)#403
100yenadmin merged 1 commit into
mainfrom
ci/viewer-tests-job

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 30, 2026

Copy link
Copy Markdown
Member

Why

The viewer is where most recent regressions landed, yet viewer/tests/ was not run by CI. CI ran only servers/engine + servers/rules + servers/voice pytest + scripts/license_check.py. Three recent viewer PRs each had to caveat "viewer tests not in CI — verified locally" (e.g. #402), so viewer regressions could slip through. This closes that gap.

What this adds

A new viewer-tests job in .github/workflows/ci.yml that runs the entire viewer/tests/ suite on push + PR, single-process. CI-config + env only — no engine/viewer/skill source touched, no wire contracts touched.

The setup mirrors the existing test job (actions/checkout + astral-sh/setup-uv) and adds exactly what the viewer suite genuinely needs:

  • actions/setup-node@v4 — a subset of viewer tests (test_recovery_timing, test_live_narration_stream, test_combat_event_cards) are a real-JSX behaviour harness: they transpile the actual .jsx through the vendored viewer/openworlds/vendor/babel-standalone-7.29.0.min.js and run it under node's vm. Babel is vendored and the harness only require()s node built-ins (fs, vm), so no npm install is needed — just a node binary. Without node these tests @unittest.skipIf themselves; installing node makes them actually run in CI.
  • A venv with pydantic + pytest — see below.

Invocation is single-process: python -m pytest viewer/tests -q -p no:xdist (no parallel workers — lean by design).

How test_portrait_gen / deps are handled (the one known local failure)

viewer/tests/test_portrait_gen.py::PortraitGenRealSubprocessTests::test_null_default_returns_placeholder_no_network is the test that fails locally on a bare box with ModuleNotFoundError: No module named 'pydantic'. Root cause (verified, not guessed):

  • The test calls server._portrait_gen(...), which shells the engine via uv run --directory servers/engine --no-project python -c <snippet>. --no-project deliberately gives a bare interpreter (so a wedged gateway can't tie up the viewer thread).
  • That snippet does import imagegenimport storefrom pydantic import ValidationError. With --no-project, pydantic is absent unless it's in the active environment. That's the env gap — not a real defect (the test's purpose is proving the null image provider opens no socket; the engine side already proves the same in servers/engine/tests/test_imagegen.py).

Fix (no source change, no weakened assertion): the job creates a venv, exports VIRTUAL_ENV + PATH so subsequent steps (and the uv run --no-project child) inherit it, and installs pydantic — the engine's declared runtime dep (servers/engine/pyproject.toml: pydantic>=2.6). I verified locally that uv run --no-project resolves the active venv, so the engine subprocess imports store/imagegen and the test runs and passes rather than erroring on import. No assertion was relaxed; the test does real work.

Green CI evidence (reproduced locally, single-process)

Baseline on origin/main (bare env): 201 ran, 1 failed (test_portrait_gen → ModuleNotFoundError: pydantic), 1 skipped — matches the caveat in the three viewer PRs.

With this job's setup (node + active venv w/ pydantic), from repo root:

$ python -m pytest viewer/tests -q -p no:xdist
200 passed, 1 skipped, 17 subtests passed in ~63s
  • The JSX/babel-harness tests run (node present) and pass.
  • test_portrait_gen (all 13, incl. the real-subprocess one) runs and passes (pydantic present).
  • The 1 remaining skip is test_roster_surface::test_known_canon_pick_resolves_its_ingested_portrait, which skipTests when there's "no ingested _private portrait for hartlebury in this checkout." _private/ is never committed, so this is correctly skipped in CI too — it's gated on private art, not an env gap, and forcing it to run is out of scope (no public fixture exists).

Which viewer tests now run in CI vs. remain uncovered

  • Now covered in CI: the full viewer/tests/ suite — all 25 test files, including the Python server-route/read-model tests and the node/babel JSX-behaviour tests and test_portrait_gen's real-engine-subprocess test.
  • Still skipped (by design, not a gap): the single test_roster_surface canon-portrait assertion that requires an ingested _private portrait (private content, never committed).

Notes for the reviewer

  • The job runs the suite as unittest-style tests under pytest (the suite is unittest.TestCase classes); both python -m unittest discover -s viewer/tests and pytest viewer/tests agree it's green. I used pytest to match the existing test job's convention.
  • .venv-viewer is created only on the CI runner and discarded with the job — nothing is committed.

Do NOT close on merge — verify on next CI run.

Summary by CodeRabbit

  • Chores
    • Enhanced continuous integration testing infrastructure with additional automated viewer test suite validation on code pushes and pull requests to improve code quality assurance.

Review Change Stack

The viewer is where most recent regressions landed, yet its test suite was
not run by CI — three viewer PRs each had to caveat 'viewer tests not in CI,
verified locally'. Add a viewer-tests job that runs the full viewer/tests/
suite on push + PR, single-process.

Setup mirrors the existing test job (checkout + astral-sh/setup-uv) plus:
- actions/setup-node: the JSX behaviour tests transpile the real .jsx through
  the VENDORED babel-standalone under node's vm. Babel is vendored and the
  harness only requires node built-ins (fs, vm), so no npm install is needed —
  just a node binary, which makes those tests RUN instead of skipIf-ing.
- a venv with pydantic + pytest: test_portrait_gen's real-subprocess test
  shells the engine via 'uv run --directory servers/engine --no-project'
  (a bare interpreter) to prove the null image provider opens no socket. That
  child resolves the ACTIVE venv, so pydantic (the engine's declared runtime
  dep) must be present for the engine to import store/imagegen. Installing it
  here makes the previously-failing test RUN and pass — no source change, no
  weakened assertion.

Single-process (-p no:xdist), no parallel workers — lean for the host.
CI-config + env only; no engine/viewer/skill source, no wire contracts.
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR adds a new viewer-tests job to the CI workflow that runs the full viewer/tests test suite. The job sets up a dedicated Python 3.12 virtual environment with pytest and pydantic dependencies, configures Node.js v20 for JSX support, and executes tests single-process using pytest.

Changes

Viewer Tests CI Job

Layer / File(s) Summary
Viewer tests job setup and execution
.github/workflows/ci.yml
New viewer-tests job installs uv, Node.js v20, creates a Python 3.12 venv with pytest and pydantic, updates environment paths, and runs pytest viewer/tests with single-process execution.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops through CI logs so bright,
New viewer tests now shine with pytest light,
With venvs spun and Node.js by its side,
The tests shall run with nowhere left to hide! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding viewer/tests to CI to close a coverage gap.
Description check ✅ Passed The description is comprehensive and complete, covering all template sections with detailed context, but is missing explicit checkbox confirmations for the Licensing/CLA section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 23-28: The viewer-tests job currently inherits broad default token
permissions; update the viewer-tests job to declare explicit least-privilege
permissions by adding a permissions block that limits the GITHUB_TOKEN to only
the scopes needed for read-only test execution (for example: set contents: read
and any other minimal read-only scopes required by your test harness) so the job
no longer inherits full default permissions.
- Around line 30-39: The workflow currently uses unpinned tags for actions
(actions/checkout@v4, astral-sh/setup-uv@v5, actions/setup-node@v4) and leaves
checkout credentials persisted; update the viewer-tests job to pin each action
to a specific commit SHA instead of the tag and modify the actions/checkout step
to include persist-credentials: false to avoid leaking tokens to checked-out
workflows. Locate the steps referencing the exact strings "actions/checkout@v4",
"astral-sh/setup-uv@v5", and "actions/setup-node@v4" and replace their uses with
the corresponding full commit SHAs, and add persist-credentials: false under the
checkout step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b352aee1-b669-47fa-8edb-1d222eb6a6d9

📥 Commits

Reviewing files that changed from the base of the PR and between ce46f61 and faa386b.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml
Comment on lines +23 to +28
viewer-tests:
# The viewer is where most recent regressions landed, but its suite was not in
# CI (three viewer PRs each had to caveat "verified locally"). This job runs the
# whole viewer/tests/ suite, single-process, on push + PR so viewer regressions
# are caught in CI like the engine/rules/voice suites.
runs-on: ubuntu-latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add explicit least-privilege permissions for this job.

This job currently inherits default token permissions, which is broader than needed for read-only test execution.

🔐 Proposed fix
   viewer-tests:
+    permissions:
+      contents: read
     # The viewer is where most recent regressions landed, but its suite was not in
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewer-tests:
# The viewer is where most recent regressions landed, but its suite was not in
# CI (three viewer PRs each had to caveat "verified locally"). This job runs the
# whole viewer/tests/ suite, single-process, on push + PR so viewer regressions
# are caught in CI like the engine/rules/voice suites.
runs-on: ubuntu-latest
viewer-tests:
permissions:
contents: read
# The viewer is where most recent regressions landed, but its suite was not in
# CI (three viewer PRs each had to caveat "verified locally"). This job runs the
# whole viewer/tests/ suite, single-process, on push + PR so viewer regressions
# are caught in CI like the engine/rules/voice suites.
runs-on: ubuntu-latest
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 23-57: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 23 - 28, The viewer-tests job
currently inherits broad default token permissions; update the viewer-tests job
to declare explicit least-privilege permissions by adding a permissions block
that limits the GITHUB_TOKEN to only the scopes needed for read-only test
execution (for example: set contents: read and any other minimal read-only
scopes required by your test harness) so the job no longer inherits full default
permissions.

Comment thread .github/workflows/ci.yml
Comment on lines +30 to +39
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install Node (for the JSX behaviour harness)
# A subset of viewer tests transpile the real .jsx through the VENDORED
# babel-standalone (viewer/openworlds/vendor/) and run it under node's `vm`.
# Babel is vendored and the harness only requires node built-ins (fs, vm), so
# NO `npm install` is needed — just a node binary on PATH. Without node these
# tests `skipIf` themselves; we install it so they actually RUN in CI.
uses: actions/setup-node@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show the referenced section of ci.yml with line numbers
if [ -f .github/workflows/ci.yml ]; then
  echo "---- .github/workflows/ci.yml (lines 1-120) ----"
  nl -ba .github/workflows/ci.yml | sed -n '1,120p'
else
  echo "Missing: .github/workflows/ci.yml"
fi

# 2) Check if persist-credentials is already set anywhere in workflows
echo "---- Searching for persist-credentials in .github/workflows ----"
rg -n "persist-credentials\s*:" .github/workflows || true

# 3) List all action uses across workflows (only 'uses:' lines)
echo "---- Listing all uses: lines across .github/workflows ----"
rg -n "^\s*uses:\s*([^\s]+)" .github/workflows || true

# 4) Narrow to the specific actions mentioned in the comment
echo "---- Searching for actions/checkout, setup-uv, setup-node usage ----"
rg -n "uses:\s*(actions/checkout@|astral-sh/setup-uv@|actions/setup-node@)" .github/workflows || true

Repository: electricsheephq/WorldOS

Length of output: 177


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if [ -f .github/workflows/ci.yml ]; then
  echo "---- .github/workflows/ci.yml (first 160 lines with line numbers) ----"
  awk 'NR<=160{printf "%d\t%s\n", NR, $0}' .github/workflows/ci.yml
else
  echo "Missing: .github/workflows/ci.yml"
fi

echo "---- Searching for persist-credentials in .github/workflows ----"
rg -n "persist-credentials\s*:" .github/workflows || true

echo "---- Listing all uses: lines across .github/workflows (uses only) ----"
rg -n "^\s*uses:\s*" .github/workflows || true

echo "---- Searching for actions/checkout, astral-sh/setup-uv, actions/setup-node usage ----"
rg -n "uses:\s*(actions/checkout@|astral-sh/setup-uv@|actions/setup-node@)" .github/workflows || true

Repository: electricsheephq/WorldOS

Length of output: 4091


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show macos-swift.yml with line numbers
if [ -f .github/workflows/macos-swift.yml ]; then
  echo "---- .github/workflows/macos-swift.yml (first 120 lines) ----"
  awk 'NR<=120{printf "%d\t%s\n", NR, $0}' .github/workflows/macos-swift.yml
fi

# Confirm there is no persist-credentials in that file
if [ -f .github/workflows/macos-swift.yml ]; then
  echo "---- persist-credentials occurrences (macos-swift.yml) ----"
  rg -n "persist-credentials\s*:" .github/workflows/macos-swift.yml || true
fi

Repository: electricsheephq/WorldOS

Length of output: 1243


Harden workflow action supply-chain usage (pin SHAs) and disable checkout credential persistence.

In .github/workflows/ci.yml’s viewer-tests job, actions/checkout@v4, astral-sh/setup-uv@v5, and actions/setup-node@v4 are only pinned to tags, and checkout does not set persist-credentials: false.

🛡️ Proposed fix
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA>
+        with:
+          persist-credentials: false
       - name: Install uv
-        uses: astral-sh/setup-uv@v5
+        uses: astral-sh/setup-uv@<FULL_LENGTH_COMMIT_SHA>
       - name: Install Node (for the JSX behaviour harness)
-        uses: actions/setup-node@v4
+        uses: actions/setup-node@<FULL_LENGTH_COMMIT_SHA>
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 30-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 30-30: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 39-39: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 30 - 39, The workflow currently uses
unpinned tags for actions (actions/checkout@v4, astral-sh/setup-uv@v5,
actions/setup-node@v4) and leaves checkout credentials persisted; update the
viewer-tests job to pin each action to a specific commit SHA instead of the tag
and modify the actions/checkout step to include persist-credentials: false to
avoid leaking tokens to checked-out workflows. Locate the steps referencing the
exact strings "actions/checkout@v4", "astral-sh/setup-uv@v5", and
"actions/setup-node@v4" and replace their uses with the corresponding full
commit SHAs, and add persist-credentials: false under the checkout step.

@100yenadmin
100yenadmin merged commit 6310750 into main May 30, 2026
10 checks passed
@100yenadmin
100yenadmin deleted the ci/viewer-tests-job branch May 30, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant