test: use sqlite locators in session lifecycle tests

This commit is contained in:
Peter Steinberger
2026-05-09 05:33:07 +01:00
parent 1e23ff88be
commit f5334129f5
3 changed files with 46 additions and 32 deletions

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import { expect, test, vi } from "vitest";
import { readTranscriptState } from "../agents/transcript/transcript-state.js";
import { getSessionEntry, upsertSessionEntry } from "../config/sessions.js";
import { createSqliteSessionTranscriptLocator } from "../config/sessions/paths.js";
import { replaceSqliteSessionTranscriptEvents } from "../config/sessions/transcript-store.sqlite.js";
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
import { withEnvAsync } from "../test-utils/env.js";
@@ -23,6 +24,10 @@ import {
const { createSessionStoreDir, openClient } = setupGatewaySessionsTestHarness();
function sqliteTranscript(sessionId: string, agentId = DEFAULT_AGENT_ID): string {
return createSqliteSessionTranscriptLocator({ agentId, sessionId });
}
test("sessions.compaction.* lists checkpoints and branches or restores from pre-compaction snapshots", async () => {
const { dir } = await createSessionStoreDir();
const fixture = await createCheckpointFixture(dir);
@@ -212,10 +217,11 @@ test("sessions.compaction.* lists checkpoints and branches or restores from pre-
test("sessions.compact without maxLines runs embedded manual compaction for checkpoint-capable flows", async () => {
const { dir } = await createSessionStoreDir();
const sessionFile = sqliteTranscript("sess-main");
replaceSqliteSessionTranscriptEvents({
agentId: DEFAULT_AGENT_ID,
sessionId: "sess-main",
transcriptPath: path.join(dir, "sess-main.jsonl"),
transcriptPath: sessionFile,
events: [
{
type: "session",
@@ -236,7 +242,7 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
agentId: "main",
sessionKey: "agent:main:main",
entry: sessionStoreEntry("sess-main", {
sessionFile: path.join(dir, "sess-main.jsonl"),
sessionFile,
thinkingLevel: "medium",
reasoningLevel: "stream",
}),
@@ -260,7 +266,7 @@ test("sessions.compact without maxLines runs embedded manual compaction for chec
expect.objectContaining({
sessionId: "sess-main",
sessionKey: "agent:main:main",
sessionFile: expect.stringMatching(/sess-main\.jsonl$/),
sessionFile,
config: expect.any(Object),
provider: expect.any(String),
model: expect.any(String),

View File

@@ -1,4 +1,3 @@
import path from "node:path";
import { expect, test } from "vitest";
import { createSqliteSessionTranscriptLocator, getSessionEntry } from "../config/sessions.js";
import { replaceSqliteSessionTranscriptEvents } from "../config/sessions/transcript-store.sqlite.js";
@@ -20,6 +19,10 @@ import {
const { createSessionStoreDir, openClient } = setupGatewaySessionsTestHarness();
function sqliteTranscript(sessionId: string): string {
return createSqliteSessionTranscriptLocator({ agentId: "main", sessionId });
}
test("sessions.delete rejects main and aborts active runs", async () => {
const { dir } = await createSessionStoreDir();
await writeSingleLineSession(dir, "sess-main", "hello");
@@ -45,7 +48,7 @@ test("sessions.delete rejects main and aborts active runs", async () => {
expect(deleted.payload?.deleted).toBe(true);
expectActiveRunCleanup(
"agent:main:discord:group:dev",
["discord:group:dev", "agent:main:discord:group:dev", "sess-active"],
["agent:main:discord:group:dev", "sess-active"],
"sess-active",
);
expect(bundleMcpRuntimeMocks.disposeSessionMcpRuntime).toHaveBeenCalledWith("sess-active");
@@ -171,7 +174,7 @@ test("sessions.delete closes ACP runtime handles before removing ACP sessions",
test("sessions.delete emits session_end with deleted reason and no replacement", async () => {
const { dir } = await createSessionStoreDir();
await writeSingleLineSession(dir, "sess-main", "hello");
const transcriptPath = path.join(dir, "sess-delete.jsonl");
const transcriptPath = sqliteTranscript("sess-delete");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-delete",
@@ -211,7 +214,7 @@ test("sessions.delete emits session_end with deleted reason and no replacement",
reason: "deleted",
});
expect((event as { sessionFile?: string } | undefined)?.sessionFile).toBe(
createSqliteSessionTranscriptLocator({ agentId: "main", sessionId: "sess-delete" }),
sqliteTranscript("sess-delete"),
);
expect((event as { nextSessionId?: string } | undefined)?.nextSessionId).toBeUndefined();
expect(context).toMatchObject({
@@ -339,7 +342,7 @@ test("sessions.delete returns unavailable when active run does not stop", async
expect(deleted.error?.message ?? "").toMatch(/still active/i);
expectActiveRunCleanup(
"agent:main:discord:group:dev",
["discord:group:dev", "agent:main:discord:group:dev", "sess-active"],
["agent:main:discord:group:dev", "sess-active"],
"sess-active",
);
expect(browserSessionTabMocks.closeTrackedBrowserTabsForSessions).not.toHaveBeenCalled();

View File

@@ -1,5 +1,3 @@
import fs from "node:fs/promises";
import path from "node:path";
import { expect, test } from "vitest";
import {
createSqliteSessionTranscriptLocator,
@@ -55,6 +53,10 @@ function expectMainHookContext(context: HookEventRecord, sessionId: string) {
expect(context.sessionId).toBe(sessionId);
}
function sqliteTranscript(sessionId: string): string {
return createSqliteSessionTranscriptLocator({ agentId: "main", sessionId });
}
test("sessions.reset emits internal command hook with reason", async () => {
const { dir } = await createSessionStoreDir();
await writeSingleLineSession(dir, "sess-main", "hello");
@@ -104,8 +106,8 @@ test("sessions.reset emits internal command hook with reason", async () => {
});
test("sessions.reset emits before_reset hook with transcript context", async () => {
const { dir } = await createSessionStoreDir();
const transcriptPath = path.join(dir, "sess-main.jsonl");
await createSessionStoreDir();
const transcriptPath = sqliteTranscript("sess-main");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-main",
@@ -147,8 +149,8 @@ test("sessions.reset emits before_reset hook with transcript context", async ()
});
test("sessions.reset emits before_reset hook with scoped SQLite transcript context", async () => {
const { dir } = await createSessionStoreDir();
const transcriptPath = path.join(dir, "missing-sess-main.jsonl");
await createSessionStoreDir();
const transcriptPath = sqliteTranscript("sess-main-sqlite");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-main-sqlite",
@@ -201,8 +203,8 @@ test("sessions.reset emits before_reset hook with scoped SQLite transcript conte
});
test("sessions.reset emits enriched session_end and session_start hooks", async () => {
const { dir } = await createSessionStoreDir();
const transcriptPath = path.join(dir, "sess-main.jsonl");
await createSessionStoreDir();
const transcriptPath = sqliteTranscript("sess-main");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-main",
@@ -281,7 +283,7 @@ test("sessions.reset returns unavailable when active run does not stop", async (
expect(reset.ok).toBe(false);
expect(reset.error?.code).toBe("UNAVAILABLE");
expect(reset.error?.message ?? "").toMatch(/still active/i);
expectActiveRunCleanup("agent:main:main", ["main", "agent:main:main", "sess-main"], "sess-main");
expectActiveRunCleanup("agent:main:main", ["agent:main:main", "sess-main"], "sess-main");
expect(beforeResetHookMocks.runBeforeReset).not.toHaveBeenCalled();
expect(waitCallCountAtSnapshotClear).toEqual([1]);
expect(browserSessionTabMocks.closeTrackedBrowserTabsForSessions).not.toHaveBeenCalled();
@@ -292,9 +294,9 @@ test("sessions.reset returns unavailable when active run does not stop", async (
});
test("sessions.reset emits before_reset for the entry actually reset in the SQLite patch", async () => {
const { dir } = await createSessionStoreDir();
const oldTranscriptPath = path.join(dir, "sess-old.jsonl");
const newTranscriptPath = path.join(dir, "sess-new.jsonl");
await createSessionStoreDir();
const oldTranscriptPath = sqliteTranscript("sess-old");
const newTranscriptPath = sqliteTranscript("sess-new");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-old",
@@ -390,8 +392,8 @@ test("sessions.create with emitCommandHooks=true fires command:new hook against
});
test("sessions.create with emitCommandHooks=true emits reset lifecycle hooks against parent (#76957)", async () => {
const { dir } = await createSessionStoreDir();
const transcriptPath = path.join(dir, "sess-parent-hooks.jsonl");
await createSessionStoreDir();
const transcriptPath = sqliteTranscript("sess-parent-hooks");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-parent-hooks",
@@ -448,17 +450,20 @@ test("sessions.create with emitCommandHooks=true emits reset lifecycle hooks aga
});
test("sessions.create with emitCommandHooks=true resets parent in place when session.dmScope is 'main' (#77434)", async () => {
const { dir } = await createSessionStoreDir();
const transcriptPath = path.join(dir, "sess-parent-dms.jsonl");
await fs.writeFile(
await createSessionStoreDir();
const transcriptPath = sqliteTranscript("sess-parent-dms");
replaceSqliteSessionTranscriptEvents({
agentId: "main",
sessionId: "sess-parent-dms",
transcriptPath,
`${JSON.stringify({
type: "message",
id: "m1",
message: { role: "user", content: "hello before /new" },
})}\n`,
"utf-8",
);
events: [
{
type: "message",
id: "m1",
message: { role: "user", content: "hello before /new" },
},
],
});
testState.sessionConfig = { dmScope: "main" };
try {