Skip to content

refactor(daemon): relocate the server runtime tests by what they assert - #3652

Merged
thymikee merged 7 commits into
mainfrom
refactor/daemon-server-tests
Oct 15, 2026
Merged

thymikee merged 7 commits into
mainfrom
refactor/daemon-server-tests

Conversation

@thymikee

@thymikee thymikee commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

Batch server of the daemon test redesign (no test exports of any kind). The 11 root tests under src/__tests__/daemon-composition/server/ and three tails (daemon-composition-assembly, daemon-composition-web-cleanup, daemon-entrypoint) are placed by what they assert, not moved wholesale. Their vi.mocks of root platform modules and of daemon internals are gone or become relative in-package mocks.

New package fixture daemon-runtime-deps-fixture.ts: a ComposeDaemonRuntimeDeps factory of inert ports, so a daemon-runtime test starts startDaemonRuntime without the root composition. Also forwardingCreateCommandSurface in the command-surface fake: its snapshot capture reaches the config backend and writes the session back through the config session store, the two seams the daemon's refusals land on.

a / b / c split

Test Kind Where
daemon-runtime-idle-reap (3) a packages/daemon/src/server/http/ (beside daemon-runtime.ts), deps fixture, relative lease-registry mock
daemon-runtime-lease-expiry (2) a same; relative lease-registry and request-execution-scope mocks
daemon-runtime-rpc-flow-failure (2) a same; relative request-router mock
daemon-runtime-recording-reaper (1) a same; host-kit/process mock kept
daemon-runtime-metadata-ownership (8) a same; owner lifecycle and gateway come from fixture overrides
daemon-runtime-recording-teardown (2) a same; platformResourceCleanupFixture instead of the root cleanup
daemon-runtime-snapshot-shutdown (2) a same; forwarding command-surface fake, the device-ready and cleanup root mocks are gone
daemon-runtime-lifecycle-shutdown (8) a same; root sendRequest replaced by an /rpc fetch
daemon-runtime-interactor-composition split a: daemon-runtime-composition.test.ts (installs what the composition supplied; provider shutdown on a lost lock). c: src/daemon-composition.test.ts (the composed resolution picks the provider that owns the device). b: src/daemon-composition-credentials.test.ts (real composition through startDaemonRuntime, an /rpc lease allocate)
daemon-runtime-xctest-device-set split a: daemon-runtime-owner-lifecycle.test.ts (configure before readiness publication; owner diagnostics reach daemon.log; startup survives a refused restore). c: src/platform-runtime-daemon-lifecycle.test.ts (the real darwin restore)
daemon-runtime-web-close-teardown (4) split a: extended close budget, close asked of the port, failure on stderr, non-web asked too. c: src/platform-runtime-resource-cleanup.test.ts (agent-browser close argv, refusal rejects, non-web never reaches it)
daemon-composition-assembly a policy gate and session callbacks bound to the server's store move to daemon-runtime-composition.test.ts; the unreadable-policy case moves with them
daemon-composition-web-cleanup c moves beside src/daemon-composition.ts; it never needed a daemon SessionStore, so the literal session names replace it
daemon-entrypoint b moves beside src/daemon.ts, inlines the one-use startDaemonRuntimeFixture (deleted) and uses the public entry only; the trackDownloadableArtifact fetch is dropped, since http-server-artifacts.test.ts serves a tracked artifact over bearer auth

Every test is named for the module it sits beside (daemon-runtime-<aspect>.test.ts in server/http/; daemon-composition-<aspect>.test.ts, daemon-entrypoint.test.ts, platform-runtime-*.test.ts in src/), so none lands in the colocation baseline.

Mocks

Every surviving mock targets the module the daemon imports relatively from inside the package, so each takes effect. Evidence: the assertions read what the mock recorded (leaseProbe.registries, ended, shutdownSteps, the failing createRequestHandler flow), and they pass.

Subpath exports

Package Before After
daemon 103 97

Deleted ./testing/ entries with no consumer left: __tests__/session-teardown.fixtures, __tests__/support/device-claim-store, application-lifecycle-recovery, server/daemon-runtime, server/server-shutdown, session-teardown-budget. (#3638 also deletes server/server-lifecycle; the manifest edits will merge on rebase.) No other package grew a subpath.

Also

  • Removes the spent APPROVED_OVER_CEILING row for packages/daemon/src/index.ts: main carries the entry now, so the gate requires it gone.
  • Seam design (for the next batches) is in the handoff: snapshot-interactor-capture is the installed InteractorResolution, and device-ready is DaemonPlatformServices.ensureLocalDeviceReady; neither needs a production change.

Validation

check:quick, check:layering, check:fallow, check:gate-manifest, check:affected --run (all green except the stale approval row, fixed in the last commit; the eager-closure suite passes) on the pre-final head.

🤖 Generated with Claude Code

View guided diff Turn on auto-fix

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.24 MB 5.24 MB 0 B
Package (unpacked) 5.24 MB 5.24 MB 0 B
Package (download) 1.57 MB 1.57 MB -11 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.1 ms 28.2 ms +0.1 ms
CLI --help 76.7 ms 77.1 ms +0.5 ms

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 24 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/daemon/src/server/http/daemon-runtime-composition.test.ts Outdated
Comment thread packages/daemon/src/__tests__/support/command-surface-fake.ts Outdated
@thymikee

Copy link
Copy Markdown
Member Author

Evidence (quality bar)

Test-count parity. main ran 47 tests in the 14 touched files (JSON reporter on a clean origin/main worktree). This head runs 53. The +6 are splits where one test file covered a daemon half and a root half; no test was dropped.

Before (main) Tests After Tests
daemon-runtime-idle-reap 3 server/http/daemon-runtime-idle-reap 3
daemon-runtime-lease-expiry 2 server/http/daemon-runtime-lease-expiry 2
daemon-runtime-rpc-flow-failure 2 server/http/daemon-runtime-rpc-flow-failure 2
daemon-runtime-recording-reaper 1 server/http/daemon-runtime-recording-reaper 1
daemon-runtime-metadata-ownership 8 server/http/daemon-runtime-metadata-ownership 8
daemon-runtime-recording-teardown 2 server/http/daemon-runtime-recording-teardown 2
daemon-runtime-snapshot-shutdown 2 server/http/daemon-runtime-snapshot-shutdown 2
daemon-runtime-lifecycle-shutdown 8 server/http/daemon-runtime-lifecycle-shutdown 8
daemon-runtime-web-close-teardown 4 server/http/daemon-runtime-web-close-teardown + src/platform-runtime-resource-cleanup 4 + 3
daemon-runtime-xctest-device-set 2 server/http/daemon-runtime-owner-lifecycle + src/platform-runtime-daemon-lifecycle 2 + 2
daemon-runtime-interactor-composition + daemon-composition-assembly 3 + 2 server/http/daemon-runtime-composition + src/daemon-composition + src/daemon-composition-credentials 4 + 1 + 1
daemon-composition-web-cleanup 2 src/daemon-composition-web-cleanup 2
daemon-entrypoint 6 src/daemon-entrypoint 6

This is a redesign, not a pure move: the daemon tests were rewritten onto the deps fixture and the tests that asserted root adapter behavior were split out, so there is no git diff -M similarity to show. The first commit adds the fixtures only; the pure relocations (git mv) are the last commit, whose renames carry import-specifier changes only.

Lane selection. vitest list --project unit-core --filesOnly selects every new path (packages/*/src/**/*.test.ts and src/**/*.test.ts); pnpm test:unit and test:coverage run that project. Nothing moved between projects, since the old paths were already in unit-core.

vi.mocks (one line each; all relative to the module the daemon imports).

  • idle-reap, lease-expiry, lifecycle-shutdown: resources/lease-registry.ts. The tests allocate a lease on leaseProbe.registries[0], which exists only if the recording subclass was constructed.
  • lease-expiry: server/request-execution-scope.ts createLeaseSessionEnd. ended equals [[leaseId, 'LEASE_EXPIRED']] and shutdownSteps equals ['cancel','drain','teardown'].
  • rpc-flow-failure: server/request-router.ts createRequestHandler. The injected throw yields the asserted 500 and one daemon_rpc_request_flow_failed event.
  • lifecycle-shutdown: core/session-store.ts (shutdownProbe.store used), http/server-shutdown.ts (the drain hook runs; closed() resolves), server/request-router.ts (shutdownProbe.dispatch is the handler that holds the request), application-lifecycle-recovery.ts (finalize spy asserted), provider-lease-expiry.ts (released asserted).
  • composition: core/session-store.ts. The store the callbacks read is the captured instance, asserted by openWebSessionNames.
  • recording-reaper: @agent-device/host-kit/process reapOwnedProcessRecordsAtStartup. The simctl-screen-recording purpose is found in reapCalls.
  • snapshot-shutdown: device-selection/dispatch-resolve (external package). The device-ready and cleanup mocks are gone.
  • web-close-teardown (root cleanup test): host-kit/command runCmd. The close argv is read from its calls.
  • platform-runtime-daemon-lifecycle: platform-apple/runner-owner restoreLegacyXctestDeviceSetRedirect. The redirect is applied to the temp path (the symlink is removed).
  • daemon-composition and daemon-composition-credentials: root platform-runtime.ts / provider-device-runtimes.ts stubs, which are root modules; both tests boot or compose without a device toolchain, which they could not do with the real gateway.

Exports. daemon 103 → 97. No other package changed; no new subpath, barrel or export *.

@thymikee

Copy link
Copy Markdown
Member Author

The code looks good at 141c006, and I found nothing that needs to change before merge. CI is still pending: all 12 remaining checks are queued, none has failed, and they must go green before merge. No conflicts. The two open inline threads, on shutdown not in finally and the snapshot.nodes read, both still apply. Please take them, since they are small.

Not blocking, take or leave: the same 25-line deviceRuntimeGateway override is pasted six times in daemon-runtime-lifecycle-shutdown.test.ts, so one local fixture would let a probe change happen in one place. The ./platform-runtime.ts and ./provider-device-runtimes.ts mock blocks are identical in src/daemon-composition.test.ts and src/daemon-composition-credentials.test.ts, so a shared factory would help. The new lost-lock test injects providerDeviceRuntimes, so the mapping inside createDaemonRuntimeDeps and the platformDaemonLifecycleOwners wiring are no longer asserted anywhere; one assertion on its output would restore that. The PR body mentions removing a spent APPROVED_OVER_CEILING row, but the diff touches no scripts/ file and the table is already empty on main, so that bullet can go.

I did not run the suite. The author's affected-test run covers the earlier head, and I only spot-checked the 47 to 53 test-count parity by reading the split files.

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/daemon/src/__tests__/support/command-surface-fake.ts Outdated
thymikee and others added 6 commits October 15, 2026 16:23
…ommand surface fake

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… it is named for

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng surface and always shut the runtime down

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y it forwards snapshots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sed to carry and fix a stale test-path comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee
thymikee force-pushed the refactor/daemon-server-tests branch from 2787df8 to fe05f52 Compare October 15, 2026 14:34
@thymikee

Copy link
Copy Markdown
Member Author

Review follow-up: rebase, mock break-proofs, the dropped artifact fetch

Rebase. Rebased onto 35c21b618; the "drop the daemon entry's spent eager-closure approval" commit is gone (#3653 removed that row on main). check:quick, check:layering, check:fallow and check:gate-manifest pass on the new head fe05f5203.

Dropped trackDownloadableArtifact fetch. The old daemon-entrypoint case fetched /artifacts/<id> with the runtime's bearer token under AGENT_DEVICE_RETAIN_ARTIFACTS=1 and asserted 200, the body and that the file stayed on disk. In packages/daemon/src/server/__tests__/http-server-artifacts.test.ts:

  • "daemon artifact downloads can be forced retained by server option" covers bearer-authenticated 200, body, and the file kept after two downloads;
  • "daemon artifact inventory lists artifacts and downloads consume them" covers the default consuming download.

Neither covers the env wiring (isEnvTruthy(env.AGENT_DEVICE_RETAIN_ARTIFACTS) in daemon-runtime.ts; no other test mentions the variable), so I added server/http/daemon-runtime-artifacts.test.ts (2 tests): with the variable set the download is 200 with the body and the file stays; without it the file is consumed. Mutation: replacing the production line with const retainArtifacts = false; fails the retain case (1/2 failing); setting the test's env to '' fails it the same way. Totals are now 55 tests across the touched files (47 before, 53 in the first head, +2 here).

vi.mock break-proofs. Each mock was repointed once at a module the code under test does not import (or, where that makes the file fail to load, the recording line was removed); counts are failing/total tests, restored afterwards.

File Mock Break Failing
daemon-runtime-idle-reap resources/lease-registry.ts drop registries.push(this) 1/3
daemon-runtime-lease-expiry resources/lease-registry.ts drop registries.push(this) 1/2
daemon-runtime-lease-expiry server/request-execution-scope.ts wrong module 2/2
daemon-runtime-rpc-flow-failure server/request-router.ts wrong module 2/2
daemon-runtime-recording-reaper @agent-device/host-kit/process wrong module (host-kit/command) 1/1
daemon-runtime-lifecycle-shutdown core/session-store.ts drop shutdownProbe.store = this 3/8
same http/server-shutdown.ts wrong module 2/8
same server/request-router.ts wrong module 1/8
same server/application-lifecycle-recovery.ts wrong module 3/8
same resources/lease-registry.ts drop registries.push(this) 2/8
same server/provider-lease-expiry.ts wrong module 2/8
daemon-runtime-composition core/session-store.ts drop storeProbe.store = this 1/4
daemon-runtime-snapshot-shutdown device-selection/dispatch-resolve wrong module 1/2
src/platform-runtime-resource-cleanup host-kit/command wrong module (host-kit/process) 3/3
src/platform-runtime-daemon-lifecycle platform-apple/runner-owner wrong module (platform-apple/simulator) 2/2
src/daemon-composition-web-cleanup platform-web wrong module 1/2
src/daemon-composition platform-runtime.ts, provider-device-runtimes.ts wrong module, each 1/1, 1/1
src/daemon-composition-credentials platform-runtime.ts, provider-device-runtimes.ts wrong module, each 1/1, 1/1

The host-kit/process mock in recording-reaper replaces reapOwnedProcessRecordsAtStartup; with it pointed elsewhere the daemon reaches the real host function and the simctl-screen-recording purpose is never recorded, so the test fails.

Small follow-ups. The comment in sessions/handlers/__tests__/snapshot.test.ts now names packages/command-registry/src/wait-positionals.test.ts. Flipping @agent-device/proxy to devDependencies waits for R11 to accept devDeps for test imports (thread mcp:231499d6); it lives in #3638, which merged, so the flip is a one-line follow-up in packages/daemon/package.json.

@thymikee

Copy link
Copy Markdown
Member Author

Mock evidence on the refined standard

Each surviving mock with the positive assertion that proves it took effect. Where none exists the break-proof from the previous comment applies and is repeated here.

File Mock Positive assertion
daemon-runtime-idle-reap resources/lease-registry.ts the probe the mock fills (leaseProbe.registries[0]) is the registry that allocates and releases the lease in the third case
daemon-runtime-lease-expiry resources/lease-registry.ts, server/request-execution-scope.ts leaseProbe.registries[0] allocates the lease; ended deep-equals [[leaseId, 'LEASE_EXPIRED']] and shutdownSteps deep-equals ['cancel','drain','teardown']
daemon-runtime-rpc-flow-failure server/request-router.ts the injected unwrappable throw reaches the response (status 500, null id, code -32000) and the logged event
daemon-runtime-recording-reaper @agent-device/host-kit/process reapOwnedProcessRecordsAtStartup the mock fills reapCalls; the test finds the simctl-screen-recording purpose in it and asserts termTimeoutMs >= 5000. The real host function never runs: with the mock pointed elsewhere the case fails (1/1)
daemon-runtime-lifecycle-shutdown core/session-store.ts shutdownProbe.store is the store the cases publish into and look sessions up in
same http/server-shutdown.ts the drain hook runs: closed() resolves and serversClosed is awaited
same server/request-router.ts shutdownProbe.dispatch is the handler that holds the request: entered() fires and publicationError is read afterwards
same server/application-lifecycle-recovery.ts shutdownProbe.finalize toHaveBeenCalledOnce() / not.toHaveBeenCalled()
same resources/lease-registry.ts, server/provider-lease-expiry.ts leaseProbe.registries[0] allocates the lease; leaseProbe.released deep-equals [lease]
daemon-runtime-composition core/session-store.ts storeProbe.store publishes the web session and its names, dir and artifact paths are compared with composed.*
daemon-runtime-snapshot-shutdown device-selection/dispatch-resolve the mocked device reaches the platform port: stopSnapshotHelper toHaveBeenCalledExactlyOnceWith(ANDROID_EMULATOR)
src/platform-runtime-resource-cleanup host-kit/command runCmd the close argv is read from mockRunCmd.mock.calls (process.execPath, close --json --session)
src/platform-runtime-daemon-lifecycle platform-apple/runner-owner the redirect is applied to the temp path: the symlink is removed and the backup restored
src/daemon-composition-web-cleanup platform-web cleanupManagedAgentBrowserOrphans mock.calls[0][1] === 'daemon-startup' and mock.calls[0][2] deep-equals the open session names
src/daemon-composition, src/daemon-composition-credentials platform-runtime.ts, provider-device-runtimes.ts (root stubs) none: they stub the heavy composition away. Break-proof: pointing either elsewhere fails 1/1 in each file (previous comment)

So the only mocks that rest on a break-proof alone are the two root composition stubs; every other row has a positive assertion, and the previous comment's break counts stand as extra evidence.

Forwarding fake

forwardingCreateCommandSurface stores and returns the backend's snapshot (flat nodes as fallback) and forwards diffSnapshot through the same seam; command-surface-fake.test.ts pins both shapes, so a fake that stores an empty tree fails its own test. #3666 deleted its second fake family accordingly.

…urface

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

The earlier findings from 141c006 are resolved at 12c318b. I found no new problems in the daemon and root test files this PR owns. The diff changes only tests, fixtures and package.json subpath exports, with no production source.

CI is still pending. All 17 checks that have not passed are queued, and none has failed. Lint, Repo Guards, Typecheck, Coverage and Integration read the moved test files and the daemon exports, so they must go green. I did not run the test suite myself, and I did not re-check the 47 to 53 test-count parity from the earlier pass.

The three bot threads on the shutdown restore, the snapshot tree read and the forwarded raw option are fixed at this head, so the author can resolve them: #3652 (comment), #3652 (comment) and #3652 (comment).

There are no conflicts. Once CI is green and the branch stays conflict-free against main, nothing else stands in the way of merge.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 15, 2026
@thymikee
thymikee added this pull request to stack #3681 October 15, 2026 15:46
@thymikee
thymikee merged commit 5033e03 into main Oct 15, 2026
22 checks passed
@thymikee
thymikee deleted the refactor/daemon-server-tests branch October 15, 2026 16:50
thymikee added a commit that referenced this pull request Oct 15, 2026
…hots (#3684)

#3642 made BackendSnapshotResult carry a required snapshot and dropped the
flat nodes fields; the daemon test relocations (#3652, #3666) landed after it
with the old shape, so main fails tsc -b.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant