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 os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
makeRuntime,
mockSessionsConfig,
runSessionsJson,
writeStore,
} from "./sessions.test-helpers.js";
// Disable colors for deterministic snapshots.
process.env.FORCE_COLOR = "0";
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,
},
},
}),
};
});
mockSessionsConfig();
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", () => {
beforeEach(() => {
vi.useFakeTimers();
@@ -126,18 +91,13 @@ describe("sessionsCommand", () => {
},
});
const { runtime, logs } = makeRuntime();
await sessionsCommand({ store, json: true }, runtime);
fs.rmSync(store);
const payload = JSON.parse(logs[0] ?? "{}") as {
const payload = await runSessionsJson<{
sessions?: Array<{
key: string;
totalTokens: number | null;
totalTokensFresh: boolean;
}>;
};
}>(sessionsCommand, store);
const main = payload.sessions?.find((row) => row.key === "main");
const group = payload.sessions?.find((row) => row.key === "discord:group:demo");
expect(main?.totalTokens).toBe(2000);
@@ -145,4 +105,47 @@ describe("sessionsCommand", () => {
expect(group?.totalTokens).toBeNull();
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 { mockSessionsConfig, runSessionsJson, writeStore } from "./sessions.test-helpers.js";
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,
},
},
}),
};
});
mockSessionsConfig();
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;
type SessionsJsonPayload = {
sessions?: Array<{
key: string;
model?: string | null;
}>;
};
const writeStore = (data: unknown) => {
const file = path.join(
os.tmpdir(),
`sessions-model-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
async function resolveSubagentModel(
runtimeFields: Record<string, unknown>,
sessionId: string,
): 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", () => {
beforeEach(() => {
@@ -57,52 +42,22 @@ describe("sessionsCommand model resolution", () => {
});
it("prefers runtime model fields for subagent sessions in JSON output", async () => {
const store = writeStore({
"agent:research:subagent:demo": {
sessionId: "subagent-1",
updatedAt: Date.now() - 2 * 60_000,
const model = await resolveSubagentModel(
{
modelProvider: "openai-codex",
model: "gpt-5.3-codex",
modelOverride: "pi:opus",
},
});
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");
"subagent-1",
);
expect(model).toBe("gpt-5.3-codex");
});
it("falls back to modelOverride when runtime model is missing", async () => {
const store = writeStore({
"agent:research:subagent:demo": {
sessionId: "subagent-2",
updatedAt: Date.now() - 2 * 60_000,
modelOverride: "openai-codex/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");
const model = await resolveSubagentModel(
{ modelOverride: "openai-codex/gpt-5.3-codex" },
"subagent-2",
);
expect(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;
}