Skip to content

Commit bebf1ef

Browse files
t3dotggclaude
andcommitted
test(server): check one-thread sweep reads against the shell snapshot
The reactor tests stub the query service, so they cannot show that a one-thread read returns the same thread as the full shell snapshot. This test seeds real SQLite rows (a linked PR, a branch PR in another project, a latest turn, a session) and asserts that readSweepSnapshot returns the same sequence, thread, and project shells as getShellSnapshot. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent 8b304dc commit bebf1ef

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

‎apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import * as ThreadPlanProgress from "../ThreadPlanProgress.ts";
3030
import { ProjectionSnapshotQuery } from "../Services/ProjectionSnapshotQuery.ts";
3131
import { encodeThreadDetailPageCursor } from "../threadDetailCursor.ts";
3232
import { projectThreadDetailSnapshot } from "../ActivityPayloadProjection.ts";
33+
import { readSweepSnapshot } from "../ThreadPullRequestReactor.ts";
3334
import { makeSqlStatementCounter } from "../../../integration/SqlStatementCounter.integration.ts";
3435

3536
const asProjectId = (value: string): ProjectId => ProjectId.make(value);
@@ -3574,6 +3575,74 @@ it.effect(
35743575
},
35753576
);
35763577

3578+
it.effect("reads one sweep thread and its projects like the shell snapshot", () => {
3579+
const layer = OrchestrationProjectionSnapshotQueryLive.pipe(
3580+
Layer.provide(ThreadBackgroundLiveness.layer),
3581+
Layer.provide(ThreadPlanProgress.layer),
3582+
Layer.provide(
3583+
Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {
3584+
resolve: () =>
3585+
Effect.succeed({
3586+
canonicalKey: "github.com/acme/web",
3587+
provider: "github",
3588+
displayName: "acme/web",
3589+
locator: {
3590+
source: "git-remote" as const,
3591+
remoteName: "origin",
3592+
remoteUrl: "https://github.com/acme/web.git",
3593+
},
3594+
}),
3595+
}),
3596+
),
3597+
Layer.provideMerge(SqlitePersistenceMemory),
3598+
);
3599+
return Effect.gen(function* () {
3600+
const sql = yield* SqlClient.SqlClient;
3601+
const query = yield* ProjectionSnapshotQuery;
3602+
yield* sql`INSERT INTO projection_projects (project_id, title, workspace_root, scripts_json, created_at, updated_at)
3603+
VALUES ('p1', 'One', '/one', '[]', '2026-09-01T00:00:00Z', '2026-09-01T00:00:00Z'),
3604+
('p2', 'Two', '/two', '[]', '2026-09-02T00:00:00Z', '2026-09-02T00:00:00Z'),
3605+
('p3', 'Three', '/three', '[]', '2026-09-03T00:00:00Z', '2026-09-03T00:00:00Z')`;
3606+
yield* sql`INSERT INTO projection_threads (thread_id, project_id, title, model_selection_json, runtime_mode, interaction_mode, branch, worktree_path, branch_pull_request_json, latest_turn_id, latest_user_message_at, pending_approval_count, snoozed_until, snoozed_at, created_at, updated_at, settled_override, settled_at)
3607+
VALUES
3608+
('t-linked', 'p1', 'Linked', '{"provider":"codex","model":"gpt-5"}', 'full-access', 'default', 'feature', '/one/wt', NULL, 'turn-1', '2026-09-02T00:00:00Z', 1, NULL, NULL, '2026-09-01T00:00:00Z', '2026-09-02T00:00:00Z', NULL, NULL),
3609+
('t-branch', 'p1', 'Branch', '{"provider":"codex","model":"gpt-5"}', 'full-access', 'default', 'other', NULL,
3610+
'{"projectId":"p2","repository":"acme/web","number":8,"url":"https://github.com/acme/web/pull/8"}',
3611+
NULL, NULL, 0, '2026-09-10T00:00:00Z', '2026-09-02T00:00:00Z', '2026-09-01T00:00:00Z', '2026-09-02T00:00:00Z', 'settled', '2026-09-03T00:00:00Z'),
3612+
('t-other', 'p3', 'Other', '{"provider":"codex","model":"gpt-5"}', 'full-access', 'default', NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, '2026-09-01T00:00:00Z', '2026-09-01T00:00:00Z', NULL, NULL)`;
3613+
yield* sql`INSERT INTO projection_thread_pull_requests (thread_id, host, repository, number, url, source, linked_at)
3614+
VALUES ('t-linked', 'github.com', 'acme/web', 7, 'https://github.com/acme/web/pull/7', 'agent', '2026-09-02T00:00:00Z')`;
3615+
yield* sql`INSERT INTO projection_turns (thread_id, turn_id, state, requested_at, started_at, completed_at, checkpoint_files_json)
3616+
VALUES ('t-linked', 'turn-1', 'completed', '2026-09-02T00:00:00Z', '2026-09-02T00:00:01Z', '2026-09-02T00:00:02Z', '[]')`;
3617+
yield* sql`INSERT INTO projection_thread_sessions (thread_id, status, provider_name, active_turn_id, last_error, updated_at)
3618+
VALUES ('t-linked', 'ready', 'codex', NULL, NULL, '2026-09-02T00:00:03Z')`;
3619+
for (const projector of Object.values(ORCHESTRATION_PROJECTOR_NAMES)) {
3620+
yield* sql`INSERT INTO projection_state (projector, last_applied_sequence, updated_at)
3621+
VALUES (${projector}, 9, '2026-09-02T00:00:03Z')`;
3622+
}
3623+
3624+
const full = yield* query.getShellSnapshot();
3625+
// The seeded fields must reach the snapshot, or the parity check is empty.
3626+
const linked = full.threads.find((thread) => thread.id === ThreadId.make("t-linked"));
3627+
assert.strictEqual(full.snapshotSequence, 9);
3628+
assert.strictEqual(linked?.linkedPullRequest?.number, 7);
3629+
assert.strictEqual(linked?.latestTurn?.turnId, asTurnId("turn-1"));
3630+
assert.strictEqual(linked?.session?.status, "ready");
3631+
3632+
for (const [threadId, projectIds] of [
3633+
[ThreadId.make("t-linked"), [asProjectId("p1")]],
3634+
// Settlement also needs the project that the saved branch PR names.
3635+
[ThreadId.make("t-branch"), [asProjectId("p1"), asProjectId("p2")]],
3636+
] as const) {
3637+
assert.deepStrictEqual(yield* readSweepSnapshot(query, threadId), {
3638+
snapshotSequence: full.snapshotSequence,
3639+
projects: full.projects.filter((project) => projectIds.includes(project.id)),
3640+
threads: full.threads.filter((thread) => thread.id === threadId),
3641+
});
3642+
}
3643+
}).pipe(Effect.provide(layer));
3644+
});
3645+
35773646
projectionSnapshotLayer("ProjectionSnapshotQuery activities by kind", (it) => {
35783647
it.effect("lists one kind across active threads only, without hydrating the threads", () =>
35793648
Effect.gen(function* () {

0 commit comments

Comments
 (0)