mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 01:51:24 +00:00
refactor: deduplicate shared helpers and test setup
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user