Skip to content

Move WorkspaceApiTests to the worker-disabled factory so background workers cannot mutate seeded queue rows (#1418) - #1419

Merged
Chris0Jeky merged 2 commits into
mainfrom
issue-1418/worker-disabled-workspace-tests
Jul 17, 2026
Merged

Move WorkspaceApiTests to the worker-disabled factory so background workers cannot mutate seeded queue rows (#1418)#1419
Chris0Jeky merged 2 commits into
mainfrom
issue-1418/worker-disabled-workspace-tests

Conversation

@Chris0Jeky

Copy link
Copy Markdown
Owner

Problem

WorkspaceApiTests.Home_ShouldReturnCurrentUserSummaryOnly flakes under full-suite CPU contention.

Root cause

WorkspaceApiTests used IClassFixture<TestWebApplicationFactory> — the worker-ENABLED base factory (UseEnvironment("Development"), Workers:EnableAutoQueueProcessing defaults true, Workers:QueuePollIntervalSeconds=1). The test seeds a capture in RequestStatus.Processing. The live LlmQueueToProposalWorker capture lane (GetOldestProcessingCaptureAsync) claims exactly that row and mutates it to a terminal state. Under contention the worker's 1s poll occasionally lands before the test's GET /api/workspace/home, flipping FailedCount 1→2 and making CapturesNeedingTriage return 3 instead of the expected 2.

This is the exact failure mode documented in HostedWorkerDisabledTestWebApplicationFactory's header comment (issue #1335).

Fix

Move the class onto HostedWorkerDisabledTestWebApplicationFactory, which removes all application background workers from the test host so no worker can pre-empt seeded queue rows. Three edit points in backend/tests/Taskdeck.Api.Tests/WorkspaceApiTests.cs (class fixture, field type, constructor parameter), plus a class comment citing #1418/#1335.

Not a product bug

GetCaptureSummaryByUserAsync is correctly user-scoped — the aggregation itself is right. This is purely a test-harness isolation issue: the seeded Processing capture was being claimed by a live worker mid-test. No test in the class depends on a live worker; every test sets statuses directly and asserts aggregation, and the Today_* seeds are likewise protected from ProposalHousekeepingWorker.

Convention

Follows the issue #1335 isolation boundary: use the worker-disabled factory for any test that seeds state an app worker polls or mutates. Isolation lives in the fixture, not in backend/src — production and every worker-dependent test class keep the workers via the base factory.

Verification

dotnet test backend/Taskdeck.sln -c Release -m:1 --filter "FullyQualifiedName~WorkspaceApiTests" run twice:

  • Run 1: Passed 12, Failed 0, Skipped 0
  • Run 2: Passed 12, Failed 0, Skipped 0

(The race is non-deterministic and won't repro reliably in isolation; these runs prove the factory swap breaks nothing.)

Closes #1418

Copilot AI review requested due to automatic review settings July 17, 2026 20:12
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Self-review (adversarial pass)

Scope: single file, 8 insertions / 3 deletions — swap the class fixture from the worker-enabled base TestWebApplicationFactory to HostedWorkerDisabledTestWebApplicationFactory, plus a citing comment.

Existing comments inspected: gemini-code-assist (consumer bot sunset — no content), copilot-pull-request-reviewer (quota-limited, could not review). No actionable bot findings; no review-thread comments.

Findings by severity: none.

  • CRITICAL/HIGH/MEDIUM/LOW: none. HostedWorkerDisabledTestWebApplicationFactory is a sealed subclass of TestWebApplicationFactory; all base members remain available, so the type swap is behavior-preserving for the fixture. No test in the class references base-factory-specific API (proven by the green build + run). The change removes only application background workers from the test host — production wiring and worker-dependent test classes are untouched (they keep the base factory).
  • Correctness of the fix: the flake was a live LlmQueueToProposalWorker claiming the seeded Processing capture mid-test; disabling workers on this host removes the pre-emption without altering the user-scoped aggregation under test.

Verification: --filter "FullyQualifiedName~WorkspaceApiTests" run twice → Passed 12 / Failed 0 both runs.

No fixes required.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

…eview proposal seeds, not Today_* seeds (#1418 review)
@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Fix evidence (lens-2 review)

Finding Severity Fix commit Verification
Class comment parenthetical imprecise: claimed Today_* seeds were protected from ProposalHousekeepingWorker, but ExpireStaleProposalsAsync only touches PendingReview proposals past ExpiresAt — the Today_* seeds (Applied/Expired/Approved) were never at risk. The genuinely housekeeping-exposed rows are the two PendingReview proposals in SeedWorkspaceHomeDataAsync. LOW 5fc5b04 Comment-only change (no code paths touched); reworded to "Home's PendingReview proposal seeds are likewise shielded from ProposalHousekeepingWorker expiry" — matches the seed method (two PendingReview proposals, asserted via ProposalsPendingReview == 2). Prior targeted runs remain the behavioral evidence: 12/12 passed twice.

All findings addressed; none skipped.

@Chris0Jeky
Chris0Jeky merged commit d0c7c1d into main Jul 17, 2026
35 checks passed
@github-project-automation github-project-automation Bot moved this from Pending to Done in Taskdeck Execution Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

2 participants