mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
test: dedupe sessions command tests and cover active filtering
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
84
src/commands/sessions.test-helpers.ts
Normal file
84
src/commands/sessions.test-helpers.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user