|
1 | | -import { describe, expect, test, mock, beforeEach } from "bun:test" |
| 1 | +import { describe, expect, test, spyOn, beforeEach, afterAll } from "bun:test" |
2 | 2 | import { clean, stripThinkTags } from "../../src/altimate/enhance-prompt" |
| 3 | +import { Config } from "@/config/config" |
| 4 | +import { Provider } from "@/provider/provider" |
| 5 | +import { LLM } from "@/session/llm" |
| 6 | + |
| 7 | +// --------------------------------------------------------------------------- |
| 8 | +// Spy-based mocking — scoped to this file and cleaned up in afterAll. |
| 9 | +// NEVER use mock.module() for shared infrastructure modules (@/util/log, |
| 10 | +// @/config/config, @/session/llm, etc.) because bun's mock.module() replaces |
| 11 | +// the module globally for the entire process with no restore mechanism, |
| 12 | +// breaking every other test file that imports the real module. |
| 13 | +// --------------------------------------------------------------------------- |
3 | 14 |
|
4 | | -// Mock Config for isAutoEnhanceEnabled tests |
5 | 15 | let mockConfig: any = {} |
6 | | -mock.module("@/config/config", () => ({ |
7 | | - Config: { |
8 | | - get: () => Promise.resolve(mockConfig), |
9 | | - }, |
10 | | -})) |
11 | | - |
12 | | -// Mock Provider and LLM for enhancePrompt tests |
13 | 16 | let mockStreamResult: string | undefined = "enhanced result" |
14 | 17 | let mockStreamShouldThrow = false |
15 | | -mock.module("@/provider/provider", () => ({ |
16 | | - Provider: { |
17 | | - defaultModel: () => |
18 | | - Promise.resolve({ providerID: "test-provider", modelID: "test-model" }), |
19 | | - getSmallModel: () => |
20 | | - Promise.resolve({ providerID: "test-provider", id: "test-small", modelID: "test-small" }), |
21 | | - getModel: () => |
22 | | - Promise.resolve({ providerID: "test-provider", id: "test-model", modelID: "test-model" }), |
23 | | - }, |
24 | | -})) |
25 | | - |
26 | | -mock.module("@/session/llm", () => ({ |
27 | | - LLM: { |
28 | | - stream: () => { |
29 | | - if (mockStreamShouldThrow) return Promise.reject(new Error("stream init failed")) |
30 | | - return Promise.resolve({ |
31 | | - // fullStream must be an async iterable (consumed by for-await in enhancePrompt) |
32 | | - fullStream: { |
33 | | - [Symbol.asyncIterator]: () => ({ |
34 | | - next: () => Promise.resolve({ done: true, value: undefined }), |
35 | | - }), |
36 | | - }, |
37 | | - text: mockStreamResult !== undefined |
38 | | - ? Promise.resolve(mockStreamResult) |
39 | | - : Promise.reject(new Error("stream text failed")), |
40 | | - }) |
| 18 | + |
| 19 | +const configSpy = spyOn(Config as any, "get").mockImplementation(() => Promise.resolve(mockConfig)) |
| 20 | + |
| 21 | +const defaultModelSpy = spyOn(Provider as any, "defaultModel").mockImplementation(() => |
| 22 | + Promise.resolve({ providerID: "test-provider", modelID: "test-model" }), |
| 23 | +) |
| 24 | +const getSmallModelSpy = spyOn(Provider as any, "getSmallModel").mockImplementation(() => |
| 25 | + Promise.resolve({ providerID: "test-provider", id: "test-small", modelID: "test-small" }), |
| 26 | +) |
| 27 | +const getModelSpy = spyOn(Provider as any, "getModel").mockImplementation(() => |
| 28 | + Promise.resolve({ providerID: "test-provider", id: "test-model", modelID: "test-model" }), |
| 29 | +) |
| 30 | + |
| 31 | +const streamSpy = spyOn(LLM as any, "stream").mockImplementation(() => { |
| 32 | + if (mockStreamShouldThrow) return Promise.reject(new Error("stream init failed")) |
| 33 | + return Promise.resolve({ |
| 34 | + fullStream: { |
| 35 | + [Symbol.asyncIterator]: () => ({ |
| 36 | + next: () => Promise.resolve({ done: true, value: undefined }), |
| 37 | + }), |
41 | 38 | }, |
42 | | - }, |
43 | | -})) |
44 | | - |
45 | | -mock.module("@/util/log", () => ({ |
46 | | - Log: { |
47 | | - create: () => ({ |
48 | | - info: () => {}, |
49 | | - error: () => {}, |
50 | | - debug: () => {}, |
51 | | - }), |
52 | | - }, |
53 | | -})) |
54 | | - |
55 | | -mock.module("@/agent/agent", () => ({ |
56 | | - Agent: {}, |
57 | | -})) |
58 | | - |
59 | | -mock.module("@/session/message-v2", () => ({ |
60 | | - MessageV2: {}, |
61 | | -})) |
62 | | - |
63 | | -let idCounter = 0 |
64 | | -mock.module("@/session/schema", () => ({ |
65 | | - MessageID: { |
66 | | - ascending: () => `msg-${++idCounter}`, |
67 | | - }, |
68 | | - SessionID: { |
69 | | - descending: () => `session-${++idCounter}`, |
70 | | - }, |
71 | | -})) |
72 | | - |
73 | | -// Import after mocking |
| 39 | + text: |
| 40 | + mockStreamResult !== undefined |
| 41 | + ? Promise.resolve(mockStreamResult) |
| 42 | + : Promise.reject(new Error("stream text failed")), |
| 43 | + }) |
| 44 | +}) |
| 45 | + |
| 46 | +afterAll(() => { |
| 47 | + configSpy.mockRestore() |
| 48 | + defaultModelSpy.mockRestore() |
| 49 | + getSmallModelSpy.mockRestore() |
| 50 | + getModelSpy.mockRestore() |
| 51 | + streamSpy.mockRestore() |
| 52 | +}) |
| 53 | + |
| 54 | +// Import enhancePrompt/isAutoEnhanceEnabled after spies are in place |
74 | 55 | const { enhancePrompt, isAutoEnhanceEnabled } = await import("../../src/altimate/enhance-prompt") |
75 | 56 |
|
76 | 57 | describe("enhance-prompt clean()", () => { |
@@ -141,7 +122,6 @@ describe("enhance-prompt clean()", () => { |
141 | 122 | }) |
142 | 123 |
|
143 | 124 | test("handles nested quotes inside code fences", () => { |
144 | | - // After fence stripping, quote stripping also triggers on surrounding quotes |
145 | 125 | expect(clean('```\n\'inner quoted\'\n```')).toBe("inner quoted") |
146 | 126 | }) |
147 | 127 | }) |
|
0 commit comments