Skip to content

refactor: test suite overhaul — real Redis, shared fixtures, E2E tests#899

Merged
PythonFZ merged 20 commits intomainfrom
refactor/test-suite-overhaul
Apr 1, 2026
Merged

refactor: test suite overhaul — real Redis, shared fixtures, E2E tests#899
PythonFZ merged 20 commits intomainfrom
refactor/test-suite-overhaul

Conversation

@PythonFZ
Copy link
Copy Markdown
Member

@PythonFZ PythonFZ commented Mar 26, 2026

Summary

  • Eliminate all mocked Redis/service overrides from 16 route test files — replace AsyncMock() and lambda: (None) with real Redis, InMemoryResultBackend, and MockSioServer via shared conftest fixtures
  • Consolidate ~3000 lines of duplicated per-file fixtures (session, client, helpers) into shared conftest.py — net deletion of 1032 lines
  • Add 15 E2E tests across 3 new files: client persistence, guest auth flow, and StateFileSource discovery against real uvicorn servers
  • Replace banned mock patterns in test_resolve_token.py, test_cli_auth.py, test_client_settings.py, test_client_api.py, and test_state_file_source.py with real server tests
  • Parametrize geometry 404 tests (5→1) and auth endpoint error tests (4→2)
  • Add tests/zndraw/README.md documenting test hierarchy, fixture rules, and mocking guidelines
  • Add # why: justification comments to all remaining monkeypatch/@patch calls

Result: 1429 tests pass, 0 failures. No real bugs discovered (mocks were not hiding production issues).

Test plan

  • Full test suite passes: uv run pytest tests/ -q → 1429 passed, 1 skipped
  • No AsyncMock() as Redis/result backend substitute in route tests
  • No lambda: (None) Redis overrides anywhere
  • No per-file session/client fixtures outside conftest.py
  • 3 E2E test files exist with real server tests
  • tests/zndraw/README.md exists and documents rules
  • All remaining patches have # why: justification comments

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added a comprehensive test-suite design spec and README defining tiers, fixture conventions, allowed/forbidden mocking patterns, and measurable E2E requirements.
  • Tests

    • Consolidated shared fixtures/helpers and standardized test patterns to remove per-file duplication.
    • Tightened mocking rules and introduced an in-memory result-backend helper for test coordination.
    • Added E2E coverage for client persistence, guest auth flows, and server discovery; consolidated/parametrized many tests.

PythonFZ and others added 15 commits March 26, 2026 12:59
Comprehensive spec for eliminating mock abuse, consolidating
duplicate fixtures, adding missing E2E tests, and establishing
testing guidelines in TESTING.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sultBackend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d fixtures

Replace per-file session/client/mock_sio fixtures and helper functions with
shared conftest fixtures (client, session, mock_sio) and helpers module
(create_test_user_in_db, create_test_room, auth_header). Convert MagicMock
SIO assertions to MockSioServer emitted list pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d fixtures

- Delete per-file session/client/redis/sio fixtures and local helpers
- Use shared client, session, mock_sio from conftest.py
- Convert SIO assertions from MagicMock to MockSioServer.emitted pattern
- Remove unused mock_sio params from tests that don't assert emissions
- Remove unused event model imports (BookmarksInvalidate, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ures

Replace per-file session/client/mock_sio fixtures with shared conftest
fixtures. Convert MagicMock SIO assertions to MockSioServer.emitted pattern.
Replace mock Redis in screenshots tests with real Redis pre-population via
RedisKey helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ures

- Delete per-file session/client/redis/sio fixtures and local helpers
- Use shared client, session, mock_sio from conftest.py
- Convert SIO assertions to MockSioServer.emitted pattern
- Remove unused imports and mock_sio/media_path params from non-asserting tests
- screenshots retains per-file client fixture for MediaPathDep override

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… fixtures

Remove per-file session/client/redis/sio fixtures and replace with shared
conftest fixtures (session, client, redis_client, mock_sio, frame_storage).
Also adds is_superuser param to create_test_user_in_db helper and converts
mock_redis AsyncMock usage in progress tests to real Redis client.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fixtures

- Delete per-file session/client/redis/sio fixtures and local helpers
- Convert SIO assertions to MockSioServer.emitted pattern
- Add is_superuser param to create_test_user_in_db helper
- Remove unused imports (Room, LockUpdate, FrameSelectionUpdate)
- Remove unused redis_client params from non-Redis-asserting tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… fixtures

Replaces per-file session/client/mock_sio fixtures and _create_user/_create_room/_auth_header
helpers with shared conftest fixtures (client, session, mock_sio, result_backend) and
helpers.py utilities (create_test_user_in_db, create_test_room, auth_header).

- test_routes_geometries.py: removed geometry_session/geometry_client/mock_sio fixtures;
  converted AsyncMock Redis to real Redis (flushed per test); seeded real Redis for
  test_delete_rejects_active_camera using RedisKey.active_cameras pattern; converted
  SIO assertions to MockSioServer.emitted pattern.
- test_isosurface.py: removed iso_session/iso_client fixtures; replaced AsyncMock
  Redis + AsyncMock result_backend with real Redis + InMemoryResultBackend from shared client.
- test_trajectory.py: removed traj_client fixture and per-file helpers; uses shared client.
- test_frames_provider_dispatch.py: removed prov_session/prov_client/prov_result_backend
  fixtures and local InMemoryResultBackend class; uses shared client/session/result_backend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…real servers

- test_resolve_token.py: replace httpx.Client mocks with real server calls (guest_login, login_with_credentials)
- test_cli_auth.py: replace StateFile and _resolve_url patches with real server + monkeypatch; keep httpx.Client mock for browser-flow login tests with # why: comments
- test_cli_agent/test_auth.py: replace StateFile patch with monkeypatch; keep httpx.Client mock for orchestration tests with # why: comments
- test_client_settings.py: replace StateFileSource.__call__ patch with StateFile(directory=tmp_path) redirect via autouse fixture
- test_client_api.py: replace three StateFileSource.__call__ patches with StateFile(directory=tmp_path) monkeypatch; remove unused unittest.mock import
- test_state_file_source.py: replace _is_pid_alive patches with real PID (os.getpid()) and _DEAD_PID=999999999; replace _is_url_healthy patches with real server_factory or _DEAD_URL; keep two patches for remote/preference logic with # why: comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add inline # why: comments to all monkeypatch.setattr, @patch, and
patch.dict calls across four test files. Each comment explains the
testing rationale (e.g., filesystem isolation, preventing real I/O,
orchestration testing) to improve code clarity and maintainability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Source

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Consolidates per-file test fixtures into shared fixtures/helpers, enforces explicit mocking constraints via new docs, updates many route tests to consume centralized fixtures (client, session, mock_sio, redis_client, result_backend), and adds several E2E tests (client persistence, guest auth, StateFileSource discovery).

Changes

Cohort / File(s) Summary
Design & README
docs/superpowers/specs/2026-03-26-test-suite-overhaul-design.md, tests/zndraw/README.md
New test-suite design spec and ZnDraw test README: defines three-tier test hierarchy, explicit mocking bans/allowances, fixture consolidation guidance, parametrization candidates, phased cleanup plan, and E2E coverage requirements.
Shared Fixtures & Helpers
tests/zndraw/conftest.py, tests/zndraw/helpers.py
Expanded client fixture to accept/inject redis_client, mock_sio, frame_storage, result_backend; added mock_sio and result_backend fixtures; added InMemoryResultBackend; create_test_user_in_db gains is_superuser kwarg.
Bulk Route Tests — Consolidation
tests/zndraw/test_auth_endpoints.py, tests/zndraw/test_chat.py, tests/zndraw/test_default_camera.py, tests/zndraw/test_frames_provider_dispatch.py, tests/zndraw/test_isosurface.py, tests/zndraw/test_progress.py, tests/zndraw/test_routes_*.py, tests/zndraw/test_routes_*, tests/zndraw/test_*
Removed per-file DB/Redis/ASGI fixtures and local helpers across many modules; migrated tests to shared fixtures (client, session, redis_client, mock_sio, result_backend) and shared helpers (create_test_user_in_db, create_test_room, auth_header); updated Socket.IO assertions to inspect mock_sio.emitted; some tests consolidated/parametrized.
E2E Tests — New
tests/zndraw/test_e2e_client_persistence.py, tests/zndraw/test_e2e_guest_auth.py, tests/zndraw/test_e2e_state_file_source.py
Added E2E modules: client persistence across disconnect/reconnect using real uvicorn server, guest-auth REST workflows with msgpack frame verification, and StateFileSource discovery against a running server/state file.
StateFile / Discovery Adjustments
tests/zndraw/test_state_file_source.py
Shifted tests to use server_factory/runtime PID and dynamic URLs, introduced _DEAD_PID/_DEAD_URL, reduced PID/health mocking in favor of runtime validation and filesystem-backed StateFile usage.
CLI / Auth / Settings Tests
tests/zndraw/test_cli.py, tests/zndraw/test_cli_agent/test_auth.py, tests/zndraw/test_cli_auth.py, tests/zndraw/test_client_api.py, tests/zndraw/test_client_settings.py, tests/zndraw/test_resolve_token.py, tests/zndraw/test_local_token_*.py
Refactored test monkeypatch/patch usage and formatting; replaced some patch mocks with monkeypatch.setattr to use test-local StateFile/tmp_path; several tests moved from HTTP-client mocking to server-backed fixtures (server, server_auth).
Formatting / Minor
tests/zndraw/test_cli.py, tests/zndraw/test_gif.py, tests/zndraw/test_local_token_auth.py
Mostly formatting and multi-line patch/monkeypatch rewrites and inline-comment clarifications without behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  participant ClientA as Client A
  participant Server as Uvicorn Server
  participant Storage as FrameStorage/DB/Redis
  participant ClientB as Client B

  ClientA->>Server: connect(room_id, token)
  Server->>Storage: persist room state / frames
  ClientA->>Server: append_frame(frame)
  Server->>Storage: store frame + metadata
  ClientA->>Server: disconnect()
  ClientB->>Server: connect(same room_id, token_or_new)
  Server->>Storage: fetch persisted frames for room_id
  Storage-->>Server: return frames
  Server-->>ClientB: deliver frames
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hopped through tests, one by one,

Shared fixtures stitched, the cleanup done.
Redis real, mocks neat and small,
E2E hops ensure frames survive all.
A joyful thump — a tidier test hall.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main refactoring objectives: consolidating test infrastructure (shared fixtures), replacing mocked dependencies with real ones (Redis), and adding end-to-end test coverage.
Docstring Coverage ✅ Passed Docstring coverage is 93.78% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/test-suite-overhaul

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 99.73492% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.74%. Comparing base (534239b) to head (9842216).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
tests/zndraw/helpers.py 89.47% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #899      +/-   ##
==========================================
- Coverage   91.94%   91.74%   -0.20%     
==========================================
  Files         242      245       +3     
  Lines       23536    22791     -745     
==========================================
- Hits        21639    20909     -730     
+ Misses       1897     1882      -15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
tests/zndraw/test_default_camera.py (1)

43-48: Consider using the auth_header helper for consistency.

This file uses inline {"Authorization": f"Bearer {token}"} dicts while other refactored test files use the auth_header(token) helper. Using the helper would improve consistency across the test suite.

Example refactor
     resp = await client.get(
         f"/v1/rooms/{room.id}/default-camera",
-        headers={"Authorization": f"Bearer {token}"},
+        headers=auth_header(token),
     )

Apply similar changes to other endpoints in this file (lines 58-62, 66-68, 81-85, 98-102, 113-117, 119-123, 127-130, 145-149, 152-155, 159-162).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_default_camera.py` around lines 43 - 48, Replace inline
Authorization header dicts like {"Authorization": f"Bearer {token}"} used in the
test HTTP calls (e.g., the client.get to "/v1/rooms/{room.id}/default-camera"
and the other requests at the listed ranges) with the existing
auth_header(token) helper for consistency; locate calls to
client.get/client.post/client.patch/client.delete in
tests/zndraw/test_default_camera.py and swap the headers argument to
headers=auth_header(token) (or remove headers when auth_header is already being
passed elsewhere) so all requests use the shared auth_header helper.
tests/zndraw/test_routes_edit_lock.py (2)

143-167: Add type annotation for mock_sio parameter.

The mock_sio parameter is missing its type annotation, unlike the same parameter in other test files (e.g., test_routes_bookmarks.py line 149).

Suggested fix
 `@pytest.mark.asyncio`
 async def test_acquire_edit_lock_broadcasts_lock_update(
     client: AsyncClient,
     session: AsyncSession,
-    mock_sio,
+    mock_sio: MockSioServer,
 ) -> None:

Also update the import at line 7:

-from helpers import auth_header, create_test_room, create_test_user_in_db
+from helpers import MockSioServer, auth_header, create_test_room, create_test_user_in_db
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_routes_edit_lock.py` around lines 143 - 167, Add a type
annotation for the mock_sio parameter in
test_acquire_edit_lock_broadcasts_lock_update (e.g., use the same mock SocketIO
server/test client type used in other tests) and update the module import to
expose that type so the name is available for annotation; modify the test
signature to annotate mock_sio with that imported type (matching other tests
like test_routes_bookmarks.py) so linters and IDEs recognize the mock_sio
fixture type.

385-413: Add type annotation for mock_sio parameter.

Same missing type annotation as noted above.

Suggested fix
 `@pytest.mark.asyncio`
 async def test_release_edit_lock_broadcasts_lock_update(
     client: AsyncClient,
     session: AsyncSession,
-    mock_sio,
+    mock_sio: MockSioServer,
 ) -> None:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_routes_edit_lock.py` around lines 385 - 413, The test
function test_release_edit_lock_broadcasts_lock_update is missing a type
annotation for the mock_sio parameter; add a simple annotation (e.g., mock_sio:
Any) and import Any from typing at the top of the test module so the parameter
is typed, or use the appropriate project fixture/interface type if available,
ensuring the signature reads
test_release_edit_lock_broadcasts_lock_update(client: AsyncClient, session:
AsyncSession, mock_sio: Any) -> None.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/specs/2026-03-26-test-suite-overhaul-design.md`:
- Around line 89-96: Update the fenced code block that lists the conftest.py
fixtures by adding a language specifier after the opening backticks (e.g.,
change ``` to ```text) so the block renders and lints correctly; ensure the
opening fence becomes ```text and the closing fence remains ``` in the same
snippet describing the conftest.py fixtures in the test-suite-overhaul design
doc.

In `@tests/zndraw/test_auth_endpoints.py`:
- Around line 111-115: The setup registration call using
client.post("/v1/auth/register", json={"email": setup_email, "password":
"password123"}) must be asserted to ensure the precondition actually succeeded;
after the await client.post(...) in the conditional that checks setup_email, add
an assertion checking the response status code (e.g., response.status_code ==
201 or response.status == 201) and optionally validate the response body to
guarantee the duplicate-email test is running from a clean, expected state.

In `@tests/zndraw/test_resolve_token.py`:
- Around line 32-38: The test named test_login_with_credentials_success
currently calls guest_login(server) and duplicates test_guest_login_success;
change it to call login_with_credentials using the admin credentials provided by
the server_auth fixture (e.g., obtain email and password from server_auth or
server_auth["admin"] and pass them to login_with_credentials(server, email,
password)) and assert the returned value is a non-empty JWT string;
alternatively, if credentials aren't available, remove this redundant test.
Ensure you update the docstring to reflect login_with_credentials and reference
the server_auth fixture in the test body.

In `@tests/zndraw/test_routes_geometries.py`:
- Around line 658-676: In
test_geometry_endpoints_return_404_for_nonexistent_room, the returned tuple from
create_test_user_in_db is unpacked into user, token but user is unused; change
the unpack to _user, token (or prefix user with an underscore) to satisfy the
linter and signal the unused variable while keeping the call to
create_test_user_in_db intact.

In `@tests/zndraw/test_state_file_source.py`:
- Around line 177-187: The tests are tripping Ruff S105/S106 due to hardcoded
token/password literals; update tests like
test_token_local_server_uses_access_token to stop using raw string secrets by
either (A) replacing literals passed into _local_entry and entry.access_token
("raw-admin", "real.jwt") with a shared test fixture or helper (e.g., a
fake_token fixture or a make_fake_token() helper) and consume that fixture in
server_factory/state_file/_make_source usages, or (B) explicitly suppress the
lint for the specific assignments by appending "# noqa: S105,S106" to the
offending lines; locate these changes around the test function name
test_token_local_server_uses_access_token and helper _local_entry to apply
consistently to the other listed tests.

---

Nitpick comments:
In `@tests/zndraw/test_default_camera.py`:
- Around line 43-48: Replace inline Authorization header dicts like
{"Authorization": f"Bearer {token}"} used in the test HTTP calls (e.g., the
client.get to "/v1/rooms/{room.id}/default-camera" and the other requests at the
listed ranges) with the existing auth_header(token) helper for consistency;
locate calls to client.get/client.post/client.patch/client.delete in
tests/zndraw/test_default_camera.py and swap the headers argument to
headers=auth_header(token) (or remove headers when auth_header is already being
passed elsewhere) so all requests use the shared auth_header helper.

In `@tests/zndraw/test_routes_edit_lock.py`:
- Around line 143-167: Add a type annotation for the mock_sio parameter in
test_acquire_edit_lock_broadcasts_lock_update (e.g., use the same mock SocketIO
server/test client type used in other tests) and update the module import to
expose that type so the name is available for annotation; modify the test
signature to annotate mock_sio with that imported type (matching other tests
like test_routes_bookmarks.py) so linters and IDEs recognize the mock_sio
fixture type.
- Around line 385-413: The test function
test_release_edit_lock_broadcasts_lock_update is missing a type annotation for
the mock_sio parameter; add a simple annotation (e.g., mock_sio: Any) and import
Any from typing at the top of the test module so the parameter is typed, or use
the appropriate project fixture/interface type if available, ensuring the
signature reads test_release_edit_lock_broadcasts_lock_update(client:
AsyncClient, session: AsyncSession, mock_sio: Any) -> None.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e1f8da5-13a6-445b-9ab0-77debdda32ad

📥 Commits

Reviewing files that changed from the base of the PR and between 534239b and 8d2cc5b.

📒 Files selected for processing (34)
  • docs/superpowers/specs/2026-03-26-test-suite-overhaul-design.md
  • tests/zndraw/README.md
  • tests/zndraw/conftest.py
  • tests/zndraw/helpers.py
  • tests/zndraw/test_auth_endpoints.py
  • tests/zndraw/test_chat.py
  • tests/zndraw/test_cli.py
  • tests/zndraw/test_cli_agent/test_auth.py
  • tests/zndraw/test_cli_auth.py
  • tests/zndraw/test_client_api.py
  • tests/zndraw/test_client_settings.py
  • tests/zndraw/test_default_camera.py
  • tests/zndraw/test_e2e_client_persistence.py
  • tests/zndraw/test_e2e_guest_auth.py
  • tests/zndraw/test_e2e_state_file_source.py
  • tests/zndraw/test_frames_provider_dispatch.py
  • tests/zndraw/test_gif.py
  • tests/zndraw/test_isosurface.py
  • tests/zndraw/test_local_token_auth.py
  • tests/zndraw/test_local_token_jwt_regression.py
  • tests/zndraw/test_progress.py
  • tests/zndraw/test_resolve_token.py
  • tests/zndraw/test_routes_bookmarks.py
  • tests/zndraw/test_routes_edit_lock.py
  • tests/zndraw/test_routes_figures.py
  • tests/zndraw/test_routes_frame_selection.py
  • tests/zndraw/test_routes_frames.py
  • tests/zndraw/test_routes_geometries.py
  • tests/zndraw/test_routes_presets.py
  • tests/zndraw/test_routes_selection_groups.py
  • tests/zndraw/test_routes_step.py
  • tests/zndraw/test_screenshots.py
  • tests/zndraw/test_state_file_source.py
  • tests/zndraw/test_trajectory.py

Comment on lines +177 to 187
def test_token_local_server_uses_access_token(state_file, server_factory):
"""Local server with access_token returns it as token."""
instance = server_factory({})
url = instance.url
pid = os.getpid()
entry = _local_entry(pid=pid, local_token="raw-admin")
entry.access_token = "real.jwt"
state_file.add_server("http://localhost:8000", entry)
state_file.add_server(url, entry)
source = _make_source(state_file)
with (
patch("zndraw.settings_sources._is_pid_alive", return_value=True),
patch("zndraw.settings_sources._is_url_healthy", return_value=True),
):
result = source()
result = source()
assert result["token"] == "real.jwt"
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

Address Ruff S105/S106 hits on test token/password literals.

Line 182, Line 183, Line 187, Line 195, Line 207, Line 218, and Line 240 currently trip security-lint rules. These are test fixtures, but they still need explicit suppression or fixture indirection to keep lint green.

✅ Minimal lint-safe patch pattern
-    entry = _local_entry(pid=pid, local_token="raw-admin")
-    entry.access_token = "real.jwt"
+    entry = _local_entry(pid=pid, local_token="raw-admin")  # noqa: S106
+    entry.access_token = "real.jwt"  # noqa: S105
@@
-    assert result["token"] == "real.jwt"
+    assert result["token"] == "real.jwt"  # noqa: S105
@@
-    state_file.add_server(url, _local_entry(pid=pid, local_token="raw-only"))
+    state_file.add_server(url, _local_entry(pid=pid, local_token="raw-only"))  # noqa: S106
@@
-            access_token="stored.jwt",
+            access_token="stored.jwt",  # noqa: S106
@@
-    assert result["token"] == "stored.jwt"
+    assert result["token"] == "stored.jwt"  # noqa: S105
@@
-            access_token="override.jwt",
+            access_token="override.jwt",  # noqa: S106

Also applies to: 190-198, 201-218, 233-248

🧰 Tools
🪛 Ruff (0.15.7)

[error] 182-182: Possible hardcoded password assigned to argument: "local_token"

(S106)


[error] 183-183: Possible hardcoded password assigned to: "access_token"

(S105)


[error] 187-187: Possible hardcoded password assigned to: "token"

(S105)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_state_file_source.py` around lines 177 - 187, The tests are
tripping Ruff S105/S106 due to hardcoded token/password literals; update tests
like test_token_local_server_uses_access_token to stop using raw string secrets
by either (A) replacing literals passed into _local_entry and entry.access_token
("raw-admin", "real.jwt") with a shared test fixture or helper (e.g., a
fake_token fixture or a make_fake_token() helper) and consume that fixture in
server_factory/state_file/_make_source usages, or (B) explicitly suppress the
lint for the specific assignments by appending "# noqa: S105,S106" to the
offending lines; locate these changes around the test function name
test_token_local_server_uses_access_token and helper _local_entry to apply
consistently to the other listed tests.

- Fix all 18 ruff errors (ASYNC109, PT006, ARG005, RUF059, E501, TC003)
- Add # why: comments on all monkeypatch/patch calls in CLI auth tests
- Refactor test_screenshots.py: replace per-file client with thin media_path fixture
- Add MockSioServer type annotations in test_routes_edit_lock.py
- Use auth_header() helper consistently in test_default_camera.py
- Fix test_login_with_credentials_success to actually test login_with_credentials
- Assert setup precondition in test_register_fails
- Add language specifier to spec code block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/zndraw/test_cli_auth.py (1)

152-156: Consider extracting repeated mock client setup to a helper or fixture.

The MagicMock with __enter__/__exit__ setup for the httpx context manager is duplicated in all three login tests (lines 152-156, 194-198, 232-236). A shared fixture could reduce this repetition.

♻️ Example helper fixture
`@pytest.fixture`
def mock_httpx_client():
    """Reusable mock httpx.Client supporting context manager protocol."""
    mock_client = MagicMock()
    mock_client.__enter__ = MagicMock(return_value=mock_client)
    mock_client.__exit__ = MagicMock(return_value=False)
    return mock_client

Then in tests:

-    mock_client = MagicMock()
-    mock_client.__enter__ = MagicMock(return_value=mock_client)
-    mock_client.__exit__ = MagicMock(return_value=False)
-    mock_client.post.return_value = challenge_resp
-    mock_client.get.side_effect = mock_get
+    mock_httpx_client.post.return_value = challenge_resp
+    mock_httpx_client.get.side_effect = mock_get
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_cli_auth.py` around lines 152 - 156, Extract the repeated
MagicMock context-manager setup into a pytest fixture (e.g., mock_httpx_client)
that returns a MagicMock with __enter__ and __exit__ configured, then update the
three tests (the ones currently creating mock_client and setting
post/get/side_effect) to accept that fixture and only set
mock_client.post.return_value and mock_client.get.side_effect inside each test;
this replaces the duplicated block that creates mock_client and its
context-manager methods and centralizes the reusable setup.
tests/zndraw/test_e2e_state_file_source.py (1)

68-80: Consider suppressing S106 lint warning for test token literal.

The static analysis flags access_token="real.jwt.token" as a potential hardcoded password. Since this is test data, consider adding a # noqa: S106 comment for lint compliance, or use a constant like _TEST_TOKEN = "real.jwt.token" # noqa: S105 at module level.

Proposed fix
+# Test token literal (not a real secret)
+_TEST_TOKEN = "real.jwt.token"  # noqa: S105
+
 def test_statefile_discovers_server_with_access_token(server_factory, tmp_path: Path):
     ...
     entry = ServerEntry(
         added_at=datetime.now(UTC),
         last_used=datetime.now(UTC),
         pid=os.getpid(),
-        access_token="real.jwt.token",
+        access_token=_TEST_TOKEN,
     )
     ...
-    assert result.get("token") == "real.jwt.token"
+    assert result.get("token") == _TEST_TOKEN
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_e2e_state_file_source.py` around lines 68 - 80, The test
currently sets a literal access_token in the ServerEntry
(access_token="real.jwt.token") which triggers S106; update the test to suppress
the lint warning by either appending a noqa (e.g., add "# noqa: S106" to the
access_token assignment) or move the value to a module-level constant like
_TEST_TOKEN = "real.jwt.token" with a noqa and use
ServerEntry(access_token=_TEST_TOKEN); modify the test in
tests/zndraw/test_e2e_state_file_source.py around the ServerEntry construction
to include one of these changes so the linter ignores the hardcoded test token.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/zndraw/test_screenshots.py`:
- Around line 24-37: The fixture media_path_fixture sets
app.dependency_overrides[get_media_path] but never removes it, leaking the
override to other tests; change the fixture to use a yield (or try/finally) so
you set app.dependency_overrides[get_media_path] = lambda: media before yielding
the media Path and then remove the override (e.g., pop or del
app.dependency_overrides[get_media_path]) after the yield to ensure cleanup;
reference the media_path_fixture function, app.dependency_overrides, and
get_media_path when making the change.

---

Nitpick comments:
In `@tests/zndraw/test_cli_auth.py`:
- Around line 152-156: Extract the repeated MagicMock context-manager setup into
a pytest fixture (e.g., mock_httpx_client) that returns a MagicMock with
__enter__ and __exit__ configured, then update the three tests (the ones
currently creating mock_client and setting post/get/side_effect) to accept that
fixture and only set mock_client.post.return_value and
mock_client.get.side_effect inside each test; this replaces the duplicated block
that creates mock_client and its context-manager methods and centralizes the
reusable setup.

In `@tests/zndraw/test_e2e_state_file_source.py`:
- Around line 68-80: The test currently sets a literal access_token in the
ServerEntry (access_token="real.jwt.token") which triggers S106; update the test
to suppress the lint warning by either appending a noqa (e.g., add "# noqa:
S106" to the access_token assignment) or move the value to a module-level
constant like _TEST_TOKEN = "real.jwt.token" with a noqa and use
ServerEntry(access_token=_TEST_TOKEN); modify the test in
tests/zndraw/test_e2e_state_file_source.py around the ServerEntry construction
to include one of these changes so the linter ignores the hardcoded test token.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f44f851-c3a2-40e4-a7ac-1d7844fb96c7

📥 Commits

Reviewing files that changed from the base of the PR and between 8d2cc5b and 5407fde.

📒 Files selected for processing (13)
  • docs/superpowers/specs/2026-03-26-test-suite-overhaul-design.md
  • tests/zndraw/helpers.py
  • tests/zndraw/test_auth_endpoints.py
  • tests/zndraw/test_cli_agent/test_auth.py
  • tests/zndraw/test_cli_auth.py
  • tests/zndraw/test_default_camera.py
  • tests/zndraw/test_e2e_state_file_source.py
  • tests/zndraw/test_local_token_jwt_regression.py
  • tests/zndraw/test_resolve_token.py
  • tests/zndraw/test_routes_edit_lock.py
  • tests/zndraw/test_routes_geometries.py
  • tests/zndraw/test_screenshots.py
  • tests/zndraw/test_state_file_source.py
✅ Files skipped from review due to trivial changes (1)
  • tests/zndraw/test_local_token_jwt_regression.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/zndraw/test_default_camera.py
  • tests/zndraw/test_routes_edit_lock.py
  • tests/zndraw/helpers.py
  • tests/zndraw/test_auth_endpoints.py

- test_screenshots.py: media_path fixture now yields and cleans up override
- test_cli_auth.py: extract mock_httpx_client fixture and _challenge_response
  helper to deduplicate login test setup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/zndraw/test_cli_auth.py (1)

190-214: Tighten the rejection/expiry assertions.

Both tests currently pass on any non-zero exit, so a generic failure would satisfy them and the 404/410-specific behavior could drift unnoticed. Since these paths are supposed to exercise distinct branches, assert the exact exit code and user-facing message, and keep the no-token side effect checked as well.

🧪 Suggested strengthening
     ):
         result = runner.invoke(app, ["auth", "login"])

-    assert result.exit_code != 0
+    assert result.exit_code == 1
+    assert "Login rejected." in result.output
+    assert state_file.get_token(server) is None
     ):
         result = runner.invoke(app, ["auth", "login"])

-    assert result.exit_code != 0
+    assert result.exit_code == 1
+    assert "Login challenge expired." in result.output
+    assert state_file.get_token(server) is None

Also applies to: 216-239

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_cli_auth.py` around lines 190 - 214, The test
test_auth_login_rejected currently only asserts result.exit_code != 0 which is
too weak; change it to assert the exact non-zero exit code expected from the CLI
(e.g., 1), assert the CLI output (result.output or result.stdout) contains the
specific user-facing message for a rejected challenge (mentioning 404 or
"rejected"/"expired" as appropriate), and assert the StateFile (via the
state_file fixture) still has no token saved; apply the same tightening to the
companion expiry test (the one around lines 216-239) so it asserts the exact
exit code, the specific expiry message (e.g., 410/expired), and that no token
was written to StateFile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/zndraw/test_cli_auth.py`:
- Around line 58-83: The test test_auth_status_with_stored_token currently only
asserts "server" in data which passes for other branches; update the assertions
to explicitly verify the stored-token branch by checking that data["server"] ==
server and data["token_source"] == "stored" (while keeping the existing setup
that mocks StateFile and _resolve_url and uses the real token), so the test
ensures status() returned the stored-token path.

---

Nitpick comments:
In `@tests/zndraw/test_cli_auth.py`:
- Around line 190-214: The test test_auth_login_rejected currently only asserts
result.exit_code != 0 which is too weak; change it to assert the exact non-zero
exit code expected from the CLI (e.g., 1), assert the CLI output (result.output
or result.stdout) contains the specific user-facing message for a rejected
challenge (mentioning 404 or "rejected"/"expired" as appropriate), and assert
the StateFile (via the state_file fixture) still has no token saved; apply the
same tightening to the companion expiry test (the one around lines 216-239) so
it asserts the exact exit code, the specific expiry message (e.g., 410/expired),
and that no token was written to StateFile.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c881e760-4e07-4fda-b536-3ee6b6574a13

📥 Commits

Reviewing files that changed from the base of the PR and between 5407fde and 12a99b3.

📒 Files selected for processing (2)
  • tests/zndraw/test_cli_auth.py
  • tests/zndraw/test_screenshots.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/zndraw/test_screenshots.py

PythonFZ and others added 2 commits March 31, 2026 10:59
…_token

Replace weak `"server" in data` check with explicit value assertions so the
test actually verifies the stored-token code path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add negative authorization test verifying guest B cannot write frames
to a room locked by guest A (423). Remove leftover _create_user /
_create_room / _auth_header aliases in test_routes_frames.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/zndraw/test_e2e_guest_auth.py (1)

146-163: Consider renaming test to match its behavior.

The test name says "other room" but actually tests unauthenticated access to the same room the guest created. The docstring correctly describes the intent. Also, adding a status assertion on room creation (line 155-159) would improve debuggability.

Suggested rename and status assertion
 `@pytest.mark.asyncio`
-async def test_guest_cannot_access_other_room_without_auth(http_client: AsyncClient):
+async def test_unauthenticated_frame_access_returns_401(http_client: AsyncClient):
     """Unauthenticated requests to frame endpoints return 401."""
     # Create a room with auth
     auth_resp = await http_client.post("/v1/auth/guest")
     token = auth_resp.json()["access_token"]
     headers = {"Authorization": f"Bearer {token}"}
 
     room_id = uuid.uuid4().hex
-    await http_client.post(
+    create_resp = await http_client.post(
         "/v1/rooms",
         json={"room_id": room_id, "copy_from": "@none"},
         headers=headers,
     )
+    assert create_resp.status_code == 201
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_e2e_guest_auth.py` around lines 146 - 163, Rename the test
function test_guest_cannot_access_other_room_without_auth to accurately describe
its behavior (e.g., test_guest_cannot_access_room_without_auth) and add an
assertion after creating the room to verify the POST /v1/rooms call succeeded
(assert the response status_code is 200/201) using the response variable from
the creation request; update the function name and add the status assertion
around the room creation call that uses headers and room_id to ensure failures
are easier to debug while keeping the rest of the test (including the
unauthenticated GET to /v1/rooms/{room_id}/frames and its 401 assertion)
unchanged.
tests/zndraw/test_routes_frames.py (1)

379-382: Inconsistent room ID format in 404 test.

This test uses string "nonexistent-room" while all other room-not-found tests use numeric 99999 (e.g., lines 137, 296, 458, 642). Either format may work, but the inconsistency makes it unclear whether string IDs are intentionally being tested here.

Consider aligning with the other tests for consistency:

Suggested fix
-        "/v1/rooms/nonexistent-room/frames/0/metadata",
+        "/v1/rooms/99999/frames/0/metadata",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_routes_frames.py` around lines 379 - 382, The test at the
client.get call using path "/v1/rooms/nonexistent-room/frames/0/metadata" is
inconsistent with other room-not-found tests that use numeric IDs; update the
room ID to match the other tests (e.g., use "99999") so the call becomes
"/v1/rooms/99999/frames/0/metadata" in the same test (leave auth_header(token)
unchanged) to keep ID format consistent across tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/zndraw/test_e2e_guest_auth.py`:
- Around line 112-124: In test_guest_write_multiple_frames, add explicit status
assertions immediately after the HTTP calls that return auth_resp and the room
creation response (the calls to http_client.post for "/v1/auth/guest" and
"/v1/rooms") before you call .json() or proceed; check that
auth_resp.status_code (and the room creation response.status_code) equals the
expected 200/201 (or appropriate success code) and include a helpful message so
failures show the HTTP status and body instead of raising KeyError later when
accessing auth_resp.json()["access_token"] or assuming room creation succeeded.

---

Nitpick comments:
In `@tests/zndraw/test_e2e_guest_auth.py`:
- Around line 146-163: Rename the test function
test_guest_cannot_access_other_room_without_auth to accurately describe its
behavior (e.g., test_guest_cannot_access_room_without_auth) and add an assertion
after creating the room to verify the POST /v1/rooms call succeeded (assert the
response status_code is 200/201) using the response variable from the creation
request; update the function name and add the status assertion around the room
creation call that uses headers and room_id to ensure failures are easier to
debug while keeping the rest of the test (including the unauthenticated GET to
/v1/rooms/{room_id}/frames and its 401 assertion) unchanged.

In `@tests/zndraw/test_routes_frames.py`:
- Around line 379-382: The test at the client.get call using path
"/v1/rooms/nonexistent-room/frames/0/metadata" is inconsistent with other
room-not-found tests that use numeric IDs; update the room ID to match the other
tests (e.g., use "99999") so the call becomes
"/v1/rooms/99999/frames/0/metadata" in the same test (leave auth_header(token)
unchanged) to keep ID format consistent across tests.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7fd27ff8-6025-416e-bc1b-57c78f93732e

📥 Commits

Reviewing files that changed from the base of the PR and between 60ee7ff and 9842216.

📒 Files selected for processing (2)
  • tests/zndraw/test_e2e_guest_auth.py
  • tests/zndraw/test_routes_frames.py

Comment on lines +112 to +124
@pytest.mark.asyncio
async def test_guest_write_multiple_frames(http_client: AsyncClient):
"""Guest writes multiple frames and reads them back by index."""
auth_resp = await http_client.post("/v1/auth/guest")
token = auth_resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}

room_id = uuid.uuid4().hex
await http_client.post(
"/v1/rooms",
json={"room_id": room_id, "copy_from": "@none"},
headers=headers,
)
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 | 🟡 Minor

Add status assertions before accessing response JSON for clearer failure messages.

If auth or room creation fails, the test will raise a KeyError or fail on later assertions with confusing messages. Adding early status checks improves debuggability.

Proposed fix
 `@pytest.mark.asyncio`
 async def test_guest_write_multiple_frames(http_client: AsyncClient):
     """Guest writes multiple frames and reads them back by index."""
     auth_resp = await http_client.post("/v1/auth/guest")
+    assert auth_resp.status_code == 200
     token = auth_resp.json()["access_token"]
     headers = {"Authorization": f"Bearer {token}"}
 
     room_id = uuid.uuid4().hex
-    await http_client.post(
+    create_resp = await http_client.post(
         "/v1/rooms",
         json={"room_id": room_id, "copy_from": "@none"},
         headers=headers,
     )
+    assert create_resp.status_code == 201
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/zndraw/test_e2e_guest_auth.py` around lines 112 - 124, In
test_guest_write_multiple_frames, add explicit status assertions immediately
after the HTTP calls that return auth_resp and the room creation response (the
calls to http_client.post for "/v1/auth/guest" and "/v1/rooms") before you call
.json() or proceed; check that auth_resp.status_code (and the room creation
response.status_code) equals the expected 200/201 (or appropriate success code)
and include a helpful message so failures show the HTTP status and body instead
of raising KeyError later when accessing auth_resp.json()["access_token"] or
assuming room creation succeeded.

@PythonFZ PythonFZ merged commit 38db250 into main Apr 1, 2026
6 checks passed
@PythonFZ PythonFZ deleted the refactor/test-suite-overhaul branch April 1, 2026 06:51
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.

2 participants