refactor: deduplicate shared helpers and test setup

This commit is contained in:
Peter Steinberger
2026-02-23 20:40:38 +00:00
parent 1f5e6444ee
commit 75423a00d6
33 changed files with 999 additions and 1112 deletions

View File

@@ -37,6 +37,67 @@ function stubSessionManager(): ExtensionContext["sessionManager"] {
return stub;
}
function createAnthropicModelFixture(overrides: Partial<Model<Api>> = {}): Model<Api> {
return {
id: "claude-opus-4-5",
name: "Claude Opus 4.5",
provider: "anthropic",
api: "anthropic" as const,
baseUrl: "https://api.anthropic.com",
contextWindow: 200000,
maxTokens: 4096,
reasoning: false,
input: ["text"] as const,
cost: { input: 15, output: 75, cacheRead: 0, cacheWrite: 0 },
...overrides,
};
}
type CompactionHandler = (event: unknown, ctx: unknown) => Promise<unknown>;
const createCompactionHandler = () => {
let compactionHandler: CompactionHandler | undefined;
const mockApi = {
on: vi.fn((event: string, handler: CompactionHandler) => {
if (event === "session_before_compact") {
compactionHandler = handler;
}
}),
} as unknown as ExtensionAPI;
compactionSafeguardExtension(mockApi);
expect(compactionHandler).toBeDefined();
return compactionHandler as CompactionHandler;
};
const createCompactionEvent = (params: { messageText: string; tokensBefore: number }) => ({
preparation: {
messagesToSummarize: [
{ role: "user", content: params.messageText, timestamp: Date.now() },
] as AgentMessage[],
turnPrefixMessages: [] as AgentMessage[],
firstKeptEntryId: "entry-1",
tokensBefore: params.tokensBefore,
fileOps: {
read: [],
edited: [],
written: [],
},
},
customInstructions: "",
signal: new AbortController().signal,
});
const createCompactionContext = (params: {
sessionManager: ExtensionContext["sessionManager"];
getApiKeyMock: ReturnType<typeof vi.fn>;
}) =>
({
model: undefined,
sessionManager: params.sessionManager,
modelRegistry: {
getApiKey: params.getApiKeyMock,
},
}) as unknown as Partial<ExtensionContext>;
describe("compaction-safeguard tool failures", () => {
it("formats tool failures with meta and summary", () => {
const messages: AgentMessage[] = [
@@ -272,18 +333,7 @@ describe("compaction-safeguard runtime registry", () => {
it("stores and retrieves model from runtime (fallback for compact.ts workflow)", () => {
const sm = {};
const model: Model<Api> = {
id: "claude-opus-4-5",
name: "Claude Opus 4.5",
provider: "anthropic",
api: "anthropic" as const,
baseUrl: "https://api.anthropic.com",
contextWindow: 200000,
maxTokens: 4096,
reasoning: false,
input: ["text"] as const,
cost: { input: 15, output: 75, cacheRead: 0, cacheWrite: 0 },
};
const model = createAnthropicModelFixture();
setCompactionSafeguardRuntime(sm, { model });
const retrieved = getCompactionSafeguardRuntime(sm);
expect(retrieved?.model).toEqual(model);
@@ -298,18 +348,7 @@ describe("compaction-safeguard runtime registry", () => {
it("stores and retrieves combined runtime values", () => {
const sm = {};
const model: Model<Api> = {
id: "claude-opus-4-5",
name: "Claude Opus 4.5",
provider: "anthropic",
api: "anthropic" as const,
baseUrl: "https://api.anthropic.com",
contextWindow: 200000,
maxTokens: 4096,
reasoning: false,
input: ["text"] as const,
cost: { input: 15, output: 75, cacheRead: 0, cacheWrite: 0 },
};
const model = createAnthropicModelFixture();
setCompactionSafeguardRuntime(sm, {
maxHistoryShare: 0.6,
contextWindowTokens: 200000,
@@ -329,72 +368,25 @@ describe("compaction-safeguard extension model fallback", () => {
// This test verifies the root-cause fix: when extensionRunner.initialize() is not called
// (as happens in compact.ts), ctx.model is undefined but runtime.model is available.
const sessionManager = stubSessionManager();
const model: Model<Api> = {
id: "claude-opus-4-5",
name: "Claude Opus 4.5",
provider: "anthropic",
api: "anthropic" as const,
baseUrl: "https://api.anthropic.com",
contextWindow: 200000,
maxTokens: 4096,
reasoning: false,
input: ["text"] as const,
cost: { input: 15, output: 75, cacheRead: 0, cacheWrite: 0 },
};
const model = createAnthropicModelFixture();
// Set up runtime with model (mimics buildEmbeddedExtensionPaths behavior)
setCompactionSafeguardRuntime(sessionManager, { model });
type CompactionHandler = (event: unknown, ctx: unknown) => Promise<unknown>;
let compactionHandler: CompactionHandler | undefined;
// Create a minimal mock ExtensionAPI that captures the handler
const mockApi = {
on: vi.fn((event: string, handler: CompactionHandler) => {
if (event === "session_before_compact") {
compactionHandler = handler;
}
}),
} as unknown as ExtensionAPI;
// Register the extension
compactionSafeguardExtension(mockApi);
// Verify handler was registered
expect(compactionHandler).toBeDefined();
// Now trigger the handler with mock data
const mockEvent = {
preparation: {
messagesToSummarize: [
{ role: "user", content: "test message", timestamp: Date.now() },
] as AgentMessage[],
turnPrefixMessages: [] as AgentMessage[],
firstKeptEntryId: "entry-1",
tokensBefore: 1000,
fileOps: {
read: [],
edited: [],
written: [],
},
},
customInstructions: "",
signal: new AbortController().signal,
};
const compactionHandler = createCompactionHandler();
const mockEvent = createCompactionEvent({
messageText: "test message",
tokensBefore: 1000,
});
const getApiKeyMock = vi.fn().mockResolvedValue(null);
// oxlint-disable-next-line typescript/no-explicit-any
const mockContext = {
model: undefined, // ctx.model is undefined (simulates compact.ts workflow)
const mockContext = createCompactionContext({
sessionManager,
modelRegistry: {
getApiKey: getApiKeyMock, // No API key should now cancel compaction
},
} as unknown as Partial<ExtensionContext>;
getApiKeyMock,
});
// Call the handler and wait for result
// oxlint-disable-next-line typescript/no-non-null-assertion
const result = (await compactionHandler!(mockEvent, mockContext)) as {
const result = (await compactionHandler(mockEvent, mockContext)) as {
cancel?: boolean;
};
@@ -414,51 +406,19 @@ describe("compaction-safeguard extension model fallback", () => {
// Do NOT set runtime.model (both ctx.model and runtime.model will be undefined)
type CompactionHandler = (event: unknown, ctx: unknown) => Promise<unknown>;
let compactionHandler: CompactionHandler | undefined;
const mockApi = {
on: vi.fn((event: string, handler: CompactionHandler) => {
if (event === "session_before_compact") {
compactionHandler = handler;
}
}),
} as unknown as ExtensionAPI;
compactionSafeguardExtension(mockApi);
expect(compactionHandler).toBeDefined();
const mockEvent = {
preparation: {
messagesToSummarize: [
{ role: "user", content: "test", timestamp: Date.now() },
] as AgentMessage[],
turnPrefixMessages: [] as AgentMessage[],
firstKeptEntryId: "entry-1",
tokensBefore: 500,
fileOps: {
read: [],
edited: [],
written: [],
},
},
customInstructions: "",
signal: new AbortController().signal,
};
const compactionHandler = createCompactionHandler();
const mockEvent = createCompactionEvent({
messageText: "test",
tokensBefore: 500,
});
const getApiKeyMock = vi.fn().mockResolvedValue(null);
// oxlint-disable-next-line typescript/no-explicit-any
const mockContext = {
model: undefined, // ctx.model is undefined
const mockContext = createCompactionContext({
sessionManager,
modelRegistry: {
getApiKey: getApiKeyMock, // Should NOT be called (early return)
},
} as unknown as Partial<ExtensionContext>;
getApiKeyMock,
});
// oxlint-disable-next-line typescript/no-non-null-assertion
const result = (await compactionHandler!(mockEvent, mockContext)) as {
const result = (await compactionHandler(mockEvent, mockContext)) as {
cancel?: boolean;
};