refactor: share selected global session test setup

This commit is contained in:
Vincent Koc
2026-06-01 09:00:47 +02:00
parent bff66a3e49
commit 5e09113ede
3 changed files with 109 additions and 132 deletions

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { expect, test, vi } from "vitest";
import type { SessionCompactionCheckpoint } from "../config/sessions.js";
import { withEnvAsync } from "../test-utils/env.js";
import {
embeddedRunMock,
@@ -21,7 +22,68 @@ import {
directSessionReq,
} from "./test/server-sessions.test-helpers.js";
const { createSessionStoreDir, openClient } = setupGatewaySessionsTestHarness();
const { createSessionStoreDir, createSelectedGlobalSessionStore, openClient } =
setupGatewaySessionsTestHarness();
type CheckpointFixture = Awaited<ReturnType<typeof createCheckpointFixture>>;
function compactionCheckpointEntry(
fixture: CheckpointFixture,
options: {
checkpointId: string;
sessionKey: string;
createdAt: number;
reason: SessionCompactionCheckpoint["reason"];
summary: string;
tokensBefore?: number;
tokensAfter?: number;
},
) {
return {
checkpointId: options.checkpointId,
sessionKey: options.sessionKey,
sessionId: fixture.sessionId,
createdAt: options.createdAt,
reason: options.reason,
summary: options.summary,
...(options.tokensBefore === undefined ? {} : { tokensBefore: options.tokensBefore }),
...(options.tokensAfter === undefined ? {} : { tokensAfter: options.tokensAfter }),
firstKeptEntryId: fixture.preCompactionLeafId,
preCompaction: {
sessionId: fixture.sessionId,
leafId: fixture.preCompactionLeafId,
},
postCompaction: {
sessionId: fixture.sessionId,
sessionFile: fixture.sessionFile,
leafId: fixture.postCompactionLeafId,
entryId: fixture.postCompactionLeafId,
},
};
}
function isCompactOperationEvent(message: unknown, phase: "start" | "end") {
const candidate = message as {
event?: unknown;
payload?: { operation?: unknown; phase?: unknown };
type?: unknown;
};
return (
candidate.type === "event" &&
candidate.event === "session.operation" &&
candidate.payload?.operation === "compact" &&
candidate.payload?.phase === phase
);
}
function expectMainCompactionResult(
compacted: { ok?: boolean; payload?: { compacted?: boolean; key?: string } | null },
expectedCompacted: boolean,
) {
expect(compacted.ok).toBe(true);
expect(compacted.payload?.key).toBe("agent:main:main");
expect(compacted.payload?.compacted).toBe(expectedCompacted);
}
test("sessions.compaction.* lists checkpoints and branches or restores from compacted transcripts", async () => {
const { dir, storePath } = await createSessionStoreDir();
@@ -29,34 +91,21 @@ test("sessions.compaction.* lists checkpoints and branches or restores from comp
expect((await fs.readdir(dir)).some((file) => file.includes(".checkpoint."))).toBe(false);
const checkpointEntryCount = fixture.session.getEntries().length;
const checkpointCreatedAt = Date.now();
const checkpointEntry = compactionCheckpointEntry(fixture, {
checkpointId: "checkpoint-1",
sessionKey: "agent:main:main",
createdAt: checkpointCreatedAt,
reason: "manual",
summary: "checkpoint summary",
tokensBefore: 123,
tokensAfter: 45,
});
const { SessionManager } = await getSessionManagerModule();
await writeSessionStore({
entries: {
main: sessionStoreEntry(fixture.sessionId, {
sessionFile: fixture.sessionFile,
compactionCheckpoints: [
{
checkpointId: "checkpoint-1",
sessionKey: "agent:main:main",
sessionId: fixture.sessionId,
createdAt: checkpointCreatedAt,
reason: "manual",
tokensBefore: 123,
tokensAfter: 45,
summary: "checkpoint summary",
firstKeptEntryId: fixture.preCompactionLeafId,
preCompaction: {
sessionId: fixture.sessionId,
leafId: fixture.preCompactionLeafId,
},
postCompaction: {
sessionId: fixture.sessionId,
sessionFile: fixture.sessionFile,
leafId: fixture.postCompactionLeafId,
entryId: fixture.postCompactionLeafId,
},
},
],
compactionCheckpoints: [checkpointEntry],
}),
},
});
@@ -101,27 +150,7 @@ test("sessions.compaction.* lists checkpoints and branches or restores from comp
expect(listedCheckpoints.ok).toBe(true);
expect(listedCheckpoints.payload?.key).toBe("agent:main:main");
expect(listedCheckpoints.payload?.checkpoints).toHaveLength(1);
expect(listedCheckpoints.payload?.checkpoints[0]).toEqual({
checkpointId: "checkpoint-1",
sessionKey: "agent:main:main",
sessionId: fixture.sessionId,
createdAt: checkpointCreatedAt,
reason: "manual",
summary: "checkpoint summary",
tokensBefore: 123,
tokensAfter: 45,
firstKeptEntryId: fixture.preCompactionLeafId,
preCompaction: {
sessionId: fixture.sessionId,
leafId: fixture.preCompactionLeafId,
},
postCompaction: {
sessionId: fixture.sessionId,
sessionFile: fixture.sessionFile,
leafId: fixture.postCompactionLeafId,
entryId: fixture.postCompactionLeafId,
},
});
expect(listedCheckpoints.payload?.checkpoints[0]).toEqual(checkpointEntry);
const checkpoint = await rpcReq<{
ok: true;
@@ -277,13 +306,7 @@ test("sessions.compaction.* lists checkpoints and branches or restores from comp
});
test("sessions.compaction.* scopes selected global checkpoints to the requested agent", async () => {
const { dir } = await createSessionStoreDir();
const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
testState.sessionStorePath = storeTemplate;
testState.sessionConfig = { scope: "global" };
testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
const mainStorePath = storeTemplate.replace("{agentId}", "main");
const workStorePath = storeTemplate.replace("{agentId}", "work");
const { mainStorePath, workStorePath } = await createSelectedGlobalSessionStore();
const workDir = path.dirname(workStorePath);
await fs.mkdir(path.dirname(mainStorePath), { recursive: true });
await fs.mkdir(workDir, { recursive: true });
@@ -291,6 +314,13 @@ test("sessions.compaction.* scopes selected global checkpoints to the requested
await fs.writeFile(mainSessionFile, `${JSON.stringify({ role: "user", content: "main" })}\n`);
const fixture = await createCheckpointFixture(workDir, { legacyPreCompactionSnapshot: false });
const checkpointCreatedAt = Date.now();
const checkpointEntry = compactionCheckpointEntry(fixture, {
checkpointId: "checkpoint-work",
sessionKey: "global",
createdAt: checkpointCreatedAt,
reason: "manual",
summary: "work checkpoint",
});
await fs.writeFile(
mainStorePath,
JSON.stringify(
@@ -305,27 +335,7 @@ test("sessions.compaction.* scopes selected global checkpoints to the requested
{
global: sessionStoreEntry(fixture.sessionId, {
sessionFile: fixture.sessionFile,
compactionCheckpoints: [
{
checkpointId: "checkpoint-work",
sessionKey: "global",
sessionId: fixture.sessionId,
createdAt: checkpointCreatedAt,
reason: "manual",
summary: "work checkpoint",
firstKeptEntryId: fixture.preCompactionLeafId,
preCompaction: {
sessionId: fixture.sessionId,
leafId: fixture.preCompactionLeafId,
},
postCompaction: {
sessionId: fixture.sessionId,
sessionFile: fixture.sessionFile,
leafId: fixture.postCompactionLeafId,
entryId: fixture.postCompactionLeafId,
},
},
],
compactionCheckpoints: [checkpointEntry],
}),
},
null,
@@ -410,22 +420,8 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
const { ws } = await openClient();
await rpcReq(ws, "sessions.subscribe", {});
const startEventPromise = onceMessage(
ws,
(message) =>
message.type === "event" &&
message.event === "session.operation" &&
(message.payload as { operation?: unknown; phase?: unknown })?.operation === "compact" &&
(message.payload as { operation?: unknown; phase?: unknown })?.phase === "start",
);
const endEventPromise = onceMessage(
ws,
(message) =>
message.type === "event" &&
message.event === "session.operation" &&
(message.payload as { operation?: unknown; phase?: unknown })?.operation === "compact" &&
(message.payload as { operation?: unknown; phase?: unknown })?.phase === "end",
);
const startEventPromise = onceMessage(ws, (message) => isCompactOperationEvent(message, "start"));
const endEventPromise = onceMessage(ws, (message) => isCompactOperationEvent(message, "end"));
const compacted = await rpcReq<{
ok: true;
key: string;
@@ -435,9 +431,7 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
key: "main",
});
expect(compacted.ok).toBe(true);
expect(compacted.payload?.key).toBe("agent:main:main");
expect(compacted.payload?.compacted).toBe(true);
expectMainCompactionResult(compacted, true);
const startEvent = await startEventPromise;
const endEvent = await endEventPromise;
const startPayload = startEvent.payload as {
@@ -568,14 +562,7 @@ test("sessions.compact treats Codex native compaction start as pending, not comp
const { ws } = await openClient();
await rpcReq(ws, "sessions.subscribe", {});
const endEventPromise = onceMessage(
ws,
(message) =>
message.type === "event" &&
message.event === "session.operation" &&
(message.payload as { operation?: unknown; phase?: unknown })?.operation === "compact" &&
(message.payload as { operation?: unknown; phase?: unknown })?.phase === "end",
);
const endEventPromise = onceMessage(ws, (message) => isCompactOperationEvent(message, "end"));
const compacted = await rpcReq<{
ok: true;
@@ -586,9 +573,7 @@ test("sessions.compact treats Codex native compaction start as pending, not comp
key: "main",
});
expect(compacted.ok).toBe(true);
expect(compacted.payload?.key).toBe("agent:main:main");
expect(compacted.payload?.compacted).toBe(false);
expectMainCompactionResult(compacted, false);
expect(compacted.payload?.result?.details).toMatchObject({
backend: "codex-app-server",
threadId: "thread-1",

View File

@@ -10,7 +10,8 @@ import {
sessionLifecycleHookMocks,
} from "./test/server-sessions.test-helpers.js";
const { createSessionStoreDir, openClient } = setupGatewaySessionsTestHarness();
const { createSessionStoreDir, createSelectedGlobalSessionStore, openClient } =
setupGatewaySessionsTestHarness();
function requireNonEmptyString(value: string | undefined, label: string): string {
if (!value) {
@@ -302,13 +303,7 @@ test("sessions.create preserves global and unknown sentinel keys", async () => {
});
test("sessions.create stores selected global sessions in the requested agent store", async () => {
const { dir } = await createSessionStoreDir();
const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
const mainStorePath = storeTemplate.replace("{agentId}", "main");
const workStorePath = storeTemplate.replace("{agentId}", "work");
testState.sessionStorePath = storeTemplate;
testState.sessionConfig = { scope: "global" };
testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
const { mainStorePath, workStorePath } = await createSelectedGlobalSessionStore();
const broadcastToConnIds = vi.fn();
const created = await directSessionReq<{
@@ -350,13 +345,7 @@ test("sessions.create stores selected global sessions in the requested agent sto
});
test("sessions.create loads selected global parent from the requested agent store", async () => {
const { dir } = await createSessionStoreDir();
const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
const mainStorePath = storeTemplate.replace("{agentId}", "main");
const workStorePath = storeTemplate.replace("{agentId}", "work");
testState.sessionStorePath = storeTemplate;
testState.sessionConfig = { scope: "global" };
testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
const { mainStorePath, workStorePath } = await createSelectedGlobalSessionStore();
try {
await writeSessionStore({
storePath: mainStorePath,
@@ -430,10 +419,7 @@ test("sessions.create loads selected global parent from the requested agent stor
});
test("sessions.get reads selected global messages from the requested agent store", async () => {
const { dir } = await createSessionStoreDir();
const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
const mainStorePath = storeTemplate.replace("{agentId}", "main");
const workStorePath = storeTemplate.replace("{agentId}", "work");
const { mainStorePath, workStorePath } = await createSelectedGlobalSessionStore();
const mainTranscriptPath = path.join(path.dirname(mainStorePath), "sess-main-global.jsonl");
const workTranscriptPath = path.join(path.dirname(workStorePath), "sess-work-global.jsonl");
await fs.mkdir(path.dirname(mainTranscriptPath), { recursive: true });
@@ -448,9 +434,6 @@ test("sessions.get reads selected global messages from the requested agent store
`${JSON.stringify({ type: "message", id: "work-msg", message: { role: "user", content: "work global" } })}\n`,
"utf-8",
);
testState.sessionStorePath = storeTemplate;
testState.sessionConfig = { scope: "global" };
testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
try {
await writeSessionStore({
storePath: mainStorePath,
@@ -487,11 +470,7 @@ test("sessions.get reads selected global messages from the requested agent store
});
test("sessions.create sends selected global initial tasks to the requested agent", async () => {
const { dir } = await createSessionStoreDir();
const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
testState.sessionStorePath = storeTemplate;
testState.sessionConfig = { scope: "global" };
testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
const { mainStorePath, workStorePath } = await createSelectedGlobalSessionStore();
const { ws } = await openClient();
const created = await rpcReq<{
@@ -510,8 +489,6 @@ test("sessions.create sends selected global initial tasks to the requested agent
const runId = requireNonEmptyString(created.payload?.runId, "selected global run id");
const wait = await rpcReq(ws, "agent.wait", { runId, timeoutMs: 1_000 });
expect(wait.ok).toBe(true);
const workStorePath = storeTemplate.replace("{agentId}", "work");
const mainStorePath = storeTemplate.replace("{agentId}", "main");
const workStore = JSON.parse(await fs.readFile(workStorePath, "utf-8")) as Record<
string,
{ sessionFile?: string }

View File

@@ -320,6 +320,20 @@ export function setupGatewaySessionsTestHarness() {
return { dir, storePath };
}
async function createSelectedGlobalSessionStore() {
const { dir } = await createSessionStoreDir();
const storeTemplate = path.join(dir, "{agentId}", "sessions.json");
testState.sessionStorePath = storeTemplate;
testState.sessionConfig = { scope: "global" };
testState.agentsConfig = { list: [{ id: "main", default: true }, { id: "work" }] };
return {
dir,
storeTemplate,
mainStorePath: storeTemplate.replace("{agentId}", "main"),
workStorePath: storeTemplate.replace("{agentId}", "work"),
};
}
async function seedActiveMainSession() {
const { dir, storePath } = await createSessionStoreDir();
await writeSingleLineSession(dir, "sess-main", "hello");
@@ -333,6 +347,7 @@ export function setupGatewaySessionsTestHarness() {
return {
createSessionStoreDir,
createSelectedGlobalSessionStore,
getHarness: requireHarness,
openClient,
seedActiveMainSession,