Files
pikasTech-HWLAB/internal/cloud/workbench-empty-session-gc.test.ts
T
2026-06-19 01:16:00 +08:00

95 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "bun:test";
import {
classifyWorkbenchEmptySessionGcCandidate,
runWorkbenchEmptySessionGc
} from "./workbench-empty-session-gc.ts";
test("workbench empty session GC archives only stale empty idle sessions", async () => {
const sessions = new Map([
["ses_empty_old", session({ id: "ses_empty_old", status: "idle", updatedAt: "2026-06-18T00:00:00.000Z" })],
["ses_empty_fresh", session({ id: "ses_empty_fresh", status: "idle", updatedAt: "2026-06-18T00:15:30.000Z" })],
["ses_with_user", session({ id: "ses_with_user", status: "idle", updatedAt: "2026-06-18T00:00:00.000Z", messages: [{ role: "user", text: "keep" }] })],
["ses_running", session({ id: "ses_running", status: "running", updatedAt: "2026-06-18T00:00:00.000Z" })],
["ses_trace", session({ id: "ses_trace", status: "idle", updatedAt: "2026-06-18T00:00:00.000Z", lastTraceId: "trc_keep" })],
["ses_completed", session({ id: "ses_completed", status: "completed", updatedAt: "2026-06-18T00:00:00.000Z" })]
]);
const store = {
async listAgentSessionsForUser(input = {}) {
assert.equal(input.ownerScoped, false);
assert.equal(input.actorRole, "admin");
return [...sessions.values()].filter((item) => item.status !== "archived");
},
async archiveAgentConversation(input = {}) {
assert.equal(input.ownerScoped, false);
assert.equal(input.actorRole, "admin");
const current = sessions.get(input.sessionId);
if (!current || current.status === "archived") return { count: 0 };
sessions.set(input.sessionId, { ...current, status: "archived", endedAt: input.now, updatedAt: input.now });
return { count: 1 };
}
};
const result = await runWorkbenchEmptySessionGc({
store,
ttlMs: 10 * 60 * 1000,
now: "2026-06-18T00:20:00.000Z"
});
assert.equal(result.ok, true);
assert.deepEqual(result.archivedSessionIds, ["ses_empty_old"]);
assert.equal(sessions.get("ses_empty_old")?.status, "archived");
assert.equal(sessions.get("ses_empty_fresh")?.status, "idle");
assert.equal(sessions.get("ses_with_user")?.status, "idle");
assert.equal(sessions.get("ses_running")?.status, "running");
assert.equal(sessions.get("ses_trace")?.status, "idle");
assert.equal(sessions.get("ses_completed")?.status, "completed");
assert.equal(result.keptByReason.ttl_not_elapsed, 1);
assert.equal(result.keptByReason.has_messages, 1);
assert.equal(result.keptByReason.running_or_admitting, 1);
assert.equal(result.keptByReason.has_trace_or_terminal_evidence, 1);
assert.equal(result.keptByReason.terminal_result_status, 1);
});
test("workbench empty session GC classifier uses canonical activity timestamp", () => {
const candidate = session({
id: "ses_touched",
status: "idle",
updatedAt: "2026-06-18T00:00:00.000Z",
session: { createdAt: "2026-06-18T00:00:00.000Z", lastTouchedAt: "2026-06-18T00:19:00.000Z", messageCount: 0 }
});
assert.deepEqual(
classifyWorkbenchEmptySessionGcCandidate(candidate, { nowMs: Date.parse("2026-06-18T00:20:00.000Z"), ttlMs: 10 * 60 * 1000 }),
{ eligible: false, reason: "ttl_not_elapsed" }
);
});
function session(input: any) {
const messages = input.messages ?? [];
return {
id: input.id,
projectId: "prj_hwpod_workbench",
agentId: "hwlab-code-agent",
status: input.status,
startedAt: input.startedAt ?? input.updatedAt,
endedAt: null,
ownerUserId: "usr_gc",
conversationId: `cnv_${input.id.slice("ses_".length)}`,
threadId: null,
lastTraceId: input.lastTraceId ?? null,
updatedAt: input.updatedAt,
session: {
source: "test",
createdAt: input.startedAt ?? input.updatedAt,
updatedAt: input.updatedAt,
messageCount: messages.length,
messages,
...(input.session ?? {}),
valuesRedacted: true,
secretMaterialStored: false
}
};
}