test: dedupe sessions command tests and cover active filtering

This commit is contained in:
Peter Steinberger
2026-02-18 05:30:51 +00:00
parent 23f2150190
commit e3292b9af1
3 changed files with 170 additions and 128 deletions

View File

@@ -1,54 +1,19 @@
import fs from "node:fs"; import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
makeRuntime,
mockSessionsConfig,
runSessionsJson,
writeStore,
} from "./sessions.test-helpers.js";
// Disable colors for deterministic snapshots. // Disable colors for deterministic snapshots.
process.env.FORCE_COLOR = "0"; process.env.FORCE_COLOR = "0";
vi.mock("../config/config.js", async (importOriginal) => { mockSessionsConfig();
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: () => ({
agents: {
defaults: {
model: { primary: "pi:opus" },
models: { "pi:opus": {} },
contextTokens: 32000,
},
},
}),
};
});
import { sessionsCommand } from "./sessions.js"; import { sessionsCommand } from "./sessions.js";
const makeRuntime = () => {
const logs: string[] = [];
return {
runtime: {
log: (msg: unknown) => logs.push(String(msg)),
error: (msg: unknown) => {
throw new Error(String(msg));
},
exit: (code: number) => {
throw new Error(`exit ${code}`);
},
},
logs,
} as const;
};
const writeStore = (data: unknown) => {
const file = path.join(
os.tmpdir(),
`sessions-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
);
fs.writeFileSync(file, JSON.stringify(data, null, 2));
return file;
};
describe("sessionsCommand", () => { describe("sessionsCommand", () => {
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers(); vi.useFakeTimers();
@@ -126,18 +91,13 @@ describe("sessionsCommand", () => {
}, },
}); });
const { runtime, logs } = makeRuntime(); const payload = await runSessionsJson<{
await sessionsCommand({ store, json: true }, runtime);
fs.rmSync(store);
const payload = JSON.parse(logs[0] ?? "{}") as {
sessions?: Array<{ sessions?: Array<{
key: string; key: string;
totalTokens: number | null; totalTokens: number | null;
totalTokensFresh: boolean; totalTokensFresh: boolean;
}>; }>;
}; }>(sessionsCommand, store);
const main = payload.sessions?.find((row) => row.key === "main"); const main = payload.sessions?.find((row) => row.key === "main");
const group = payload.sessions?.find((row) => row.key === "discord:group:demo"); const group = payload.sessions?.find((row) => row.key === "discord:group:demo");
expect(main?.totalTokens).toBe(2000); expect(main?.totalTokens).toBe(2000);
@@ -145,4 +105,47 @@ describe("sessionsCommand", () => {
expect(group?.totalTokens).toBeNull(); expect(group?.totalTokens).toBeNull();
expect(group?.totalTokensFresh).toBe(false); expect(group?.totalTokensFresh).toBe(false);
}); });
it("applies --active filtering in JSON output", async () => {
const store = writeStore(
{
recent: {
sessionId: "recent",
updatedAt: Date.now() - 5 * 60_000,
model: "pi:opus",
},
stale: {
sessionId: "stale",
updatedAt: Date.now() - 45 * 60_000,
model: "pi:opus",
},
},
"sessions-active",
);
const payload = await runSessionsJson<{
sessions?: Array<{
key: string;
}>;
}>(sessionsCommand, store, { active: "10" });
expect(payload.sessions?.map((row) => row.key)).toEqual(["recent"]);
});
it("rejects invalid --active values", async () => {
const store = writeStore(
{
demo: {
sessionId: "demo",
updatedAt: Date.now() - 5 * 60_000,
},
},
"sessions-active-invalid",
);
const { runtime, errors } = makeRuntime();
await expect(sessionsCommand({ store, active: "0" }, runtime)).rejects.toThrow("exit 1");
expect(errors[0]).toContain("--active must be a positive integer");
fs.rmSync(store);
});
}); });

View File

@@ -1,50 +1,35 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { mockSessionsConfig, runSessionsJson, writeStore } from "./sessions.test-helpers.js";
vi.mock("../config/config.js", async (importOriginal) => { mockSessionsConfig();
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: () => ({
agents: {
defaults: {
model: { primary: "pi:opus" },
models: { "pi:opus": {} },
contextTokens: 32000,
},
},
}),
};
});
import { sessionsCommand } from "./sessions.js"; import { sessionsCommand } from "./sessions.js";
const makeRuntime = () => { type SessionsJsonPayload = {
const logs: string[] = []; sessions?: Array<{
return { key: string;
runtime: { model?: string | null;
log: (msg: unknown) => logs.push(String(msg)), }>;
error: (msg: unknown) => {
throw new Error(String(msg));
},
exit: (code: number) => {
throw new Error(`exit ${code}`);
},
},
logs,
} as const;
}; };
const writeStore = (data: unknown) => { async function resolveSubagentModel(
const file = path.join( runtimeFields: Record<string, unknown>,
os.tmpdir(), sessionId: string,
`sessions-model-${Date.now()}-${Math.random().toString(16).slice(2)}.json`, ): Promise<string | null | undefined> {
const store = writeStore(
{
"agent:research:subagent:demo": {
sessionId,
updatedAt: Date.now() - 2 * 60_000,
...runtimeFields,
},
},
"sessions-model",
); );
fs.writeFileSync(file, JSON.stringify(data, null, 2));
return file; const payload = await runSessionsJson<SessionsJsonPayload>(sessionsCommand, store);
}; return payload.sessions?.find((row) => row.key === "agent:research:subagent:demo")?.model;
}
describe("sessionsCommand model resolution", () => { describe("sessionsCommand model resolution", () => {
beforeEach(() => { beforeEach(() => {
@@ -57,52 +42,22 @@ describe("sessionsCommand model resolution", () => {
}); });
it("prefers runtime model fields for subagent sessions in JSON output", async () => { it("prefers runtime model fields for subagent sessions in JSON output", async () => {
const store = writeStore({ const model = await resolveSubagentModel(
"agent:research:subagent:demo": { {
sessionId: "subagent-1",
updatedAt: Date.now() - 2 * 60_000,
modelProvider: "openai-codex", modelProvider: "openai-codex",
model: "gpt-5.3-codex", model: "gpt-5.3-codex",
modelOverride: "pi:opus", modelOverride: "pi:opus",
}, },
}); "subagent-1",
);
const { runtime, logs } = makeRuntime(); expect(model).toBe("gpt-5.3-codex");
await sessionsCommand({ store, json: true }, runtime);
fs.rmSync(store);
const payload = JSON.parse(logs[0] ?? "{}") as {
sessions?: Array<{
key: string;
model?: string | null;
}>;
};
const subagent = payload.sessions?.find((row) => row.key === "agent:research:subagent:demo");
expect(subagent?.model).toBe("gpt-5.3-codex");
}); });
it("falls back to modelOverride when runtime model is missing", async () => { it("falls back to modelOverride when runtime model is missing", async () => {
const store = writeStore({ const model = await resolveSubagentModel(
"agent:research:subagent:demo": { { modelOverride: "openai-codex/gpt-5.3-codex" },
sessionId: "subagent-2", "subagent-2",
updatedAt: Date.now() - 2 * 60_000, );
modelOverride: "openai-codex/gpt-5.3-codex", expect(model).toBe("gpt-5.3-codex");
},
});
const { runtime, logs } = makeRuntime();
await sessionsCommand({ store, json: true }, runtime);
fs.rmSync(store);
const payload = JSON.parse(logs[0] ?? "{}") as {
sessions?: Array<{
key: string;
model?: string | null;
}>;
};
const subagent = payload.sessions?.find((row) => row.key === "agent:research:subagent:demo");
expect(subagent?.model).toBe("gpt-5.3-codex");
}); });
}); });

View File

@@ -0,0 +1,84 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
export function mockSessionsConfig() {
vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: () => ({
agents: {
defaults: {
model: { primary: "pi:opus" },
models: { "pi:opus": {} },
contextTokens: 32000,
},
},
}),
};
});
}
export function makeRuntime(params?: { throwOnError?: boolean }): {
runtime: RuntimeEnv;
logs: string[];
errors: string[];
} {
const logs: string[] = [];
const errors: string[] = [];
const throwOnError = params?.throwOnError ?? false;
return {
runtime: {
log: (msg: unknown) => logs.push(String(msg)),
error: (msg: unknown) => {
errors.push(String(msg));
if (throwOnError) {
throw new Error(String(msg));
}
},
exit: (code: number) => {
throw new Error(`exit ${code}`);
},
},
logs,
errors,
};
}
export function writeStore(data: unknown, prefix = "sessions"): string {
const file = path.join(
os.tmpdir(),
`${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
);
fs.writeFileSync(file, JSON.stringify(data, null, 2));
return file;
}
export async function runSessionsJson<T>(
run: (
opts: { json?: boolean; store?: string; active?: string },
runtime: RuntimeEnv,
) => Promise<void>,
store: string,
options?: {
active?: string;
},
): Promise<T> {
const { runtime, logs } = makeRuntime();
try {
await run(
{
store,
json: true,
active: options?.active,
},
runtime,
);
} finally {
fs.rmSync(store, { force: true });
}
return JSON.parse(logs[0] ?? "{}") as T;
}