Archived
109 lines
5.0 KiB
TypeScript
109 lines
5.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { normalizeSessionCleanupRequest, normalizeSessionCleanupThresholds, planSessionCleanup } from "./sessionCleanup.js";
|
|
import type { PiSessionListEntry } from "./piSessionService.js";
|
|
import type { ArchivedSessionRecord } from "./sessionArchiveStore.js";
|
|
|
|
describe("session cleanup planning", () => {
|
|
it("plans cleanup by strict cutoffs and groups counts by stored cwd", () => {
|
|
const now = new Date("2026-06-25T00:00:00.000Z");
|
|
const archivedRecords: ArchivedSessionRecord[] = [
|
|
archivedRecord("already-archived", "/unregistered", "2026-06-20T00:00:00.000Z"),
|
|
archivedRecord("delete-old", "/other", "2026-06-14T23:59:59.999Z"),
|
|
archivedRecord("keep-exact", "/other", "2026-06-15T00:00:00.000Z"),
|
|
];
|
|
|
|
const plan = planSessionCleanup({
|
|
now,
|
|
thresholds: { archiveIdleDays: 30, deleteArchivedDays: 10 },
|
|
archivedRecords,
|
|
sessions: [
|
|
sessionEntry("archive-old", "/unregistered", "2026-05-25T23:59:59.999Z"),
|
|
sessionEntry("keep-exact", "/unregistered", "2026-05-26T00:00:00.000Z"),
|
|
sessionEntry("keep-new", "/unregistered", "2026-05-26T00:00:00.001Z"),
|
|
sessionEntry("already-archived", "/unregistered", "2026-05-01T00:00:00.000Z"),
|
|
],
|
|
});
|
|
|
|
expect(plan.archiveInputs.map((input) => input.sessionId)).toEqual(["archive-old"]);
|
|
expect(plan.deleteRecords.map((record) => record.sessionId)).toEqual(["delete-old"]);
|
|
expect(plan.projects).toEqual([
|
|
{ cwd: "/other", archiveCount: 0, deleteCount: 1 },
|
|
{ cwd: "/unregistered", archiveCount: 1, deleteCount: 0 },
|
|
]);
|
|
expect(plan.totals).toEqual({ archiveCount: 1, deleteCount: 1 });
|
|
});
|
|
|
|
it("filters cleanup candidates to selected project cwd paths", () => {
|
|
const plan = planSessionCleanup({
|
|
now: new Date("2026-06-25T00:00:00.000Z"),
|
|
thresholds: { archiveIdleDays: 30, deleteArchivedDays: 30 },
|
|
projectCwds: ["/repo-a"],
|
|
sessions: [
|
|
sessionEntry("archive-a", "/repo-a", "2026-05-01T00:00:00.000Z"),
|
|
sessionEntry("archive-b", "/repo-b", "2026-05-01T00:00:00.000Z"),
|
|
],
|
|
archivedRecords: [
|
|
archivedRecord("delete-a", "/repo-a", "2026-05-01T00:00:00.000Z"),
|
|
archivedRecord("delete-b", "/repo-b", "2026-05-01T00:00:00.000Z"),
|
|
],
|
|
});
|
|
|
|
expect(plan.archiveInputs.map((input) => input.sessionId)).toEqual(["archive-a"]);
|
|
expect(plan.deleteRecords.map((record) => record.sessionId)).toEqual(["delete-a"]);
|
|
expect(plan.projects).toEqual([{ cwd: "/repo-a", archiveCount: 1, deleteCount: 1 }]);
|
|
expect(plan.totals).toEqual({ archiveCount: 1, deleteCount: 1 });
|
|
});
|
|
|
|
it("skips archive and delete candidates that are busy in memory", () => {
|
|
const plan = planSessionCleanup({
|
|
now: new Date("2026-06-25T00:00:00.000Z"),
|
|
thresholds: { archiveIdleDays: 1, deleteArchivedDays: 1 },
|
|
sessions: [sessionEntry("busy-open", "/repo", "2026-06-01T00:00:00.000Z")],
|
|
archivedRecords: [archivedRecord("busy-archived", "/repo", "2026-06-01T00:00:00.000Z")],
|
|
activeSessions: [
|
|
{ sessionId: "busy-open", hasActiveWork: true },
|
|
{ sessionId: "busy-archived", hasActiveWork: true },
|
|
],
|
|
});
|
|
|
|
expect(plan.archiveInputs).toHaveLength(0);
|
|
expect(plan.deleteRecords).toHaveLength(0);
|
|
expect(plan.skippedBusySessionIds).toEqual(["busy-archived", "busy-open"]);
|
|
expect(plan.totals).toEqual({ archiveCount: 0, deleteCount: 0 });
|
|
});
|
|
|
|
it("validates optional runtime thresholds", () => {
|
|
expect(normalizeSessionCleanupThresholds({ archiveIdleDays: 30, deleteArchivedDays: null })).toEqual({ archiveIdleDays: 30 });
|
|
expect(normalizeSessionCleanupThresholds({})).toEqual({});
|
|
expect(() => normalizeSessionCleanupThresholds({ archiveIdleDays: -1 })).toThrow("archiveIdleDays field must be a non-negative integer");
|
|
expect(() => normalizeSessionCleanupThresholds({ deleteArchivedDays: 1.5 })).toThrow("deleteArchivedDays field must be a non-negative integer");
|
|
expect(() => normalizeSessionCleanupThresholds({ archiveIdleDays: "30" })).toThrow("archiveIdleDays field must be a non-negative integer");
|
|
});
|
|
|
|
it("validates optional selected project cwd paths", () => {
|
|
expect(normalizeSessionCleanupRequest({ archiveIdleDays: 30, projectCwds: ["/repo", "/repo"] })).toEqual({
|
|
thresholds: { archiveIdleDays: 30 },
|
|
projectCwds: ["/repo"],
|
|
});
|
|
expect(normalizeSessionCleanupRequest({ projectCwds: null })).toEqual({ thresholds: {} });
|
|
expect(() => normalizeSessionCleanupRequest({ projectCwds: ["/repo", 1] })).toThrow("projectCwds field must be an array of strings");
|
|
});
|
|
});
|
|
|
|
function sessionEntry(id: string, cwd: string, modified: string): PiSessionListEntry {
|
|
return {
|
|
id,
|
|
cwd,
|
|
path: `/sessions/${id}.jsonl`,
|
|
created: new Date("2026-01-01T00:00:00.000Z"),
|
|
modified: new Date(modified),
|
|
messageCount: 1,
|
|
firstMessage: "hello",
|
|
allMessagesText: "hello",
|
|
};
|
|
}
|
|
|
|
function archivedRecord(sessionId: string, cwd: string, archivedAt: string): ArchivedSessionRecord {
|
|
return { sessionId, cwd, archivedAt };
|
|
}
|