test: share context lookup helpers

This commit is contained in:
Peter Steinberger 2026-03-14 02:26:52 +00:00
parent f0179d3b4a
commit dfcc2fae9f
1 changed files with 42 additions and 60 deletions

View File

@ -1,8 +1,13 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
function mockContextModuleDeps(loadConfigImpl: () => unknown) { type DiscoveredModel = { id: string; contextWindow: number };
function mockContextDeps(params: {
loadConfig: () => unknown;
discoveredModels?: DiscoveredModel[];
}) {
vi.doMock("../config/config.js", () => ({ vi.doMock("../config/config.js", () => ({
loadConfig: loadConfigImpl, loadConfig: params.loadConfig,
})); }));
vi.doMock("./models-config.js", () => ({ vi.doMock("./models-config.js", () => ({
ensureOpenClawModelsJson: vi.fn(async () => {}), ensureOpenClawModelsJson: vi.fn(async () => {}),
@ -13,29 +18,42 @@ function mockContextModuleDeps(loadConfigImpl: () => unknown) {
vi.doMock("./pi-model-discovery.js", () => ({ vi.doMock("./pi-model-discovery.js", () => ({
discoverAuthStorage: vi.fn(() => ({})), discoverAuthStorage: vi.fn(() => ({})),
discoverModels: vi.fn(() => ({ discoverModels: vi.fn(() => ({
getAll: () => [], getAll: () => params.discoveredModels ?? [],
})), })),
})); }));
} }
function mockContextModuleDeps(loadConfigImpl: () => unknown) {
mockContextDeps({ loadConfig: loadConfigImpl });
}
// Shared mock setup used by multiple tests. // Shared mock setup used by multiple tests.
function mockDiscoveryDeps( function mockDiscoveryDeps(
models: Array<{ id: string; contextWindow: number }>, models: DiscoveredModel[],
configModels?: Record<string, { models: Array<{ id: string; contextWindow: number }> }>, configModels?: Record<string, { models: Array<{ id: string; contextWindow: number }> }>,
) { ) {
vi.doMock("../config/config.js", () => ({ mockContextDeps({
loadConfig: () => ({ models: configModels ? { providers: configModels } : {} }), loadConfig: () => ({ models: configModels ? { providers: configModels } : {} }),
})); discoveredModels: models,
vi.doMock("./models-config.js", () => ({ });
ensureOpenClawModelsJson: vi.fn(async () => {}), }
}));
vi.doMock("./agent-paths.js", () => ({ function createContextOverrideConfig(provider: string, model: string, contextWindow: number) {
resolveOpenClawAgentDir: () => "/tmp/openclaw-agent", return {
})); models: {
vi.doMock("./pi-model-discovery.js", () => ({ providers: {
discoverAuthStorage: vi.fn(() => ({})), [provider]: {
discoverModels: vi.fn(() => ({ getAll: () => models })), models: [{ id: model, contextWindow }],
})); },
},
},
};
}
async function importResolveContextTokensForModel() {
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
return resolveContextTokensForModel;
} }
describe("lookupContextTokens", () => { describe("lookupContextTokens", () => {
@ -150,18 +168,8 @@ describe("lookupContextTokens", () => {
{ id: "google-gemini-cli/gemini-3.1-pro-preview", contextWindow: 1_048_576 }, { id: "google-gemini-cli/gemini-3.1-pro-preview", contextWindow: 1_048_576 },
]); ]);
const cfg = { const cfg = createContextOverrideConfig("google-gemini-cli", "gemini-3.1-pro-preview", 200_000);
models: { const resolveContextTokensForModel = await importResolveContextTokensForModel();
providers: {
"google-gemini-cli": {
models: [{ id: "gemini-3.1-pro-preview", contextWindow: 200_000 }],
},
},
},
};
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
const result = resolveContextTokensForModel({ const result = resolveContextTokensForModel({
cfg: cfg as never, cfg: cfg as never,
@ -174,18 +182,8 @@ describe("lookupContextTokens", () => {
it("resolveContextTokensForModel honors configured overrides when provider keys use mixed case", async () => { it("resolveContextTokensForModel honors configured overrides when provider keys use mixed case", async () => {
mockDiscoveryDeps([{ id: "openrouter/anthropic/claude-sonnet-4-5", contextWindow: 1_048_576 }]); mockDiscoveryDeps([{ id: "openrouter/anthropic/claude-sonnet-4-5", contextWindow: 1_048_576 }]);
const cfg = { const cfg = createContextOverrideConfig(" OpenRouter ", "anthropic/claude-sonnet-4-5", 200_000);
models: { const resolveContextTokensForModel = await importResolveContextTokensForModel();
providers: {
" OpenRouter ": {
models: [{ id: "anthropic/claude-sonnet-4-5", contextWindow: 200_000 }],
},
},
},
};
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
const result = resolveContextTokensForModel({ const result = resolveContextTokensForModel({
cfg: cfg as never, cfg: cfg as never,
@ -202,16 +200,8 @@ describe("lookupContextTokens", () => {
// Real callers (status.summary.ts) always pass cfg when provider is explicit. // Real callers (status.summary.ts) always pass cfg when provider is explicit.
mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]); mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]);
const cfg = { const cfg = createContextOverrideConfig("google", "gemini-2.5-pro", 2_000_000);
models: { const resolveContextTokensForModel = await importResolveContextTokensForModel();
providers: {
google: { models: [{ id: "gemini-2.5-pro", contextWindow: 2_000_000 }] },
},
},
};
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
// Google with explicit cfg: config direct scan wins before any cache lookup. // Google with explicit cfg: config direct scan wins before any cache lookup.
const googleResult = resolveContextTokensForModel({ const googleResult = resolveContextTokensForModel({
@ -272,16 +262,8 @@ describe("lookupContextTokens", () => {
// window and misreport context limits for the OpenRouter session. // window and misreport context limits for the OpenRouter session.
mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]); mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]);
const cfg = { const cfg = createContextOverrideConfig("google", "gemini-2.5-pro", 2_000_000);
models: { const resolveContextTokensForModel = await importResolveContextTokensForModel();
providers: {
google: { models: [{ id: "gemini-2.5-pro", contextWindow: 2_000_000 }] },
},
},
};
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
// model-only call (no explicit provider) must NOT apply config direct scan. // model-only call (no explicit provider) must NOT apply config direct scan.
// Falls through to bare cache lookup: "google/gemini-2.5-pro" → 999k ✓. // Falls through to bare cache lookup: "google/gemini-2.5-pro" → 999k ✓.