Skip to content

Commit 3095093

Browse files
committed
refactor + tests
1 parent dde1668 commit 3095093

File tree

3 files changed

+453
-51
lines changed

3 files changed

+453
-51
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
2+
import { MessageEnhancer } from "../messageEnhancer"
3+
import { ProviderSettings, ClineMessage } from "@roo-code/types"
4+
import { TelemetryService } from "@roo-code/telemetry"
5+
import * as singleCompletionHandlerModule from "../../../utils/single-completion-handler"
6+
import { ProviderSettingsManager } from "../../config/ProviderSettingsManager"
7+
8+
// Mock dependencies
9+
vi.mock("../../../utils/single-completion-handler")
10+
vi.mock("@roo-code/telemetry")
11+
12+
describe("MessageEnhancer", () => {
13+
let mockProviderSettingsManager: ProviderSettingsManager
14+
let mockSingleCompletionHandler: ReturnType<typeof vi.fn>
15+
16+
const mockApiConfiguration: ProviderSettings = {
17+
apiProvider: "openai",
18+
apiKey: "test-key",
19+
apiModelId: "gpt-4",
20+
}
21+
22+
const mockListApiConfigMeta = [
23+
{ id: "config1", name: "Config 1" },
24+
{ id: "config2", name: "Config 2" },
25+
]
26+
27+
beforeEach(() => {
28+
// Reset all mocks
29+
vi.clearAllMocks()
30+
31+
// Mock provider settings manager
32+
mockProviderSettingsManager = {
33+
getProfile: vi.fn().mockResolvedValue({
34+
name: "Enhancement Config",
35+
apiProvider: "anthropic",
36+
apiKey: "enhancement-key",
37+
apiModelId: "claude-3",
38+
}),
39+
} as any
40+
41+
// Mock single completion handler
42+
mockSingleCompletionHandler = vi.fn().mockResolvedValue("Enhanced prompt text")
43+
vi.mocked(singleCompletionHandlerModule).singleCompletionHandler = mockSingleCompletionHandler
44+
45+
// Mock TelemetryService
46+
vi.mocked(TelemetryService).hasInstance = vi.fn().mockReturnValue(true)
47+
// Mock the instance getter
48+
Object.defineProperty(TelemetryService, "instance", {
49+
get: vi.fn().mockReturnValue({
50+
capturePromptEnhanced: vi.fn(),
51+
}),
52+
configurable: true,
53+
})
54+
})
55+
56+
afterEach(() => {
57+
vi.restoreAllMocks()
58+
})
59+
60+
describe("enhanceMessage", () => {
61+
it("should enhance a simple message successfully", async () => {
62+
const result = await MessageEnhancer.enhanceMessage({
63+
text: "Write a function to calculate fibonacci",
64+
apiConfiguration: mockApiConfiguration,
65+
listApiConfigMeta: mockListApiConfigMeta,
66+
providerSettingsManager: mockProviderSettingsManager,
67+
})
68+
69+
expect(result.success).toBe(true)
70+
expect(result.enhancedText).toBe("Enhanced prompt text")
71+
expect(result.error).toBeUndefined()
72+
73+
// Verify single completion handler was called with correct prompt
74+
expect(mockSingleCompletionHandler).toHaveBeenCalledWith(
75+
mockApiConfiguration,
76+
expect.stringContaining("Write a function to calculate fibonacci"),
77+
)
78+
})
79+
80+
it("should use enhancement API config when provided", async () => {
81+
const result = await MessageEnhancer.enhanceMessage({
82+
text: "Test prompt",
83+
apiConfiguration: mockApiConfiguration,
84+
customSupportPrompts: {},
85+
listApiConfigMeta: mockListApiConfigMeta,
86+
enhancementApiConfigId: "config2",
87+
providerSettingsManager: mockProviderSettingsManager,
88+
})
89+
90+
expect(result.success).toBe(true)
91+
expect(mockProviderSettingsManager.getProfile).toHaveBeenCalledWith({ id: "config2" })
92+
93+
// Verify the enhancement config was used instead of default
94+
const expectedConfig = {
95+
apiProvider: "anthropic",
96+
apiKey: "enhancement-key",
97+
apiModelId: "claude-3",
98+
}
99+
expect(mockSingleCompletionHandler).toHaveBeenCalledWith(expectedConfig, expect.any(String))
100+
})
101+
102+
it("should include task history when enabled", async () => {
103+
const mockClineMessages: ClineMessage[] = [
104+
{ type: "ask", text: "Create a React component", ts: 1000 },
105+
{ type: "say", say: "text", text: "I'll create a React component for you", ts: 2000 },
106+
{ type: "ask", text: "Add props to the component", ts: 3000 },
107+
{ type: "say", say: "reasoning", text: "Using tool", ts: 4000 }, // Should be filtered out
108+
]
109+
110+
const result = await MessageEnhancer.enhanceMessage({
111+
text: "Improve the component",
112+
apiConfiguration: mockApiConfiguration,
113+
listApiConfigMeta: mockListApiConfigMeta,
114+
includeTaskHistoryInEnhance: true,
115+
currentClineMessages: mockClineMessages,
116+
providerSettingsManager: mockProviderSettingsManager,
117+
})
118+
119+
expect(result.success).toBe(true)
120+
121+
// Verify the prompt includes task history
122+
const calledPrompt = mockSingleCompletionHandler.mock.calls[0][1]
123+
expect(calledPrompt).toContain("Improve the component")
124+
expect(calledPrompt).toContain("previous conversation context")
125+
expect(calledPrompt).toContain("User: Create a React component")
126+
expect(calledPrompt).toContain("Assistant: I'll create a React component for you")
127+
expect(calledPrompt).toContain("User: Add props to the component")
128+
expect(calledPrompt).not.toContain("Using tool") // reasoning messages should be filtered
129+
})
130+
131+
it("should limit task history to last 10 messages", async () => {
132+
// Create 15 messages
133+
const mockClineMessages: ClineMessage[] = Array.from({ length: 15 }, (_, i) => ({
134+
type: i % 2 === 0 ? "ask" : "say",
135+
say: i % 2 === 1 ? "text" : undefined,
136+
text: `Message ${i + 1}`,
137+
ts: i * 1000,
138+
})) as ClineMessage[]
139+
140+
await MessageEnhancer.enhanceMessage({
141+
text: "Test",
142+
apiConfiguration: mockApiConfiguration,
143+
listApiConfigMeta: mockListApiConfigMeta,
144+
includeTaskHistoryInEnhance: true,
145+
currentClineMessages: mockClineMessages,
146+
providerSettingsManager: mockProviderSettingsManager,
147+
})
148+
149+
const calledPrompt = mockSingleCompletionHandler.mock.calls[0][1]
150+
151+
// Should include messages 6-15 (last 10)
152+
expect(calledPrompt).toContain("Message 6")
153+
expect(calledPrompt).toContain("Message 15")
154+
expect(calledPrompt).not.toContain("Message 5")
155+
})
156+
157+
it("should truncate long messages in task history", async () => {
158+
const longText = "A".repeat(600) // 600 characters
159+
const mockClineMessages: ClineMessage[] = [{ type: "ask", text: longText, ts: 1000 }]
160+
161+
await MessageEnhancer.enhanceMessage({
162+
text: "Test",
163+
apiConfiguration: mockApiConfiguration,
164+
listApiConfigMeta: mockListApiConfigMeta,
165+
includeTaskHistoryInEnhance: true,
166+
currentClineMessages: mockClineMessages,
167+
providerSettingsManager: mockProviderSettingsManager,
168+
})
169+
170+
const calledPrompt = mockSingleCompletionHandler.mock.calls[0][1]
171+
172+
// Should truncate to 500 chars + "..."
173+
expect(calledPrompt).toContain("A".repeat(500) + "...")
174+
expect(calledPrompt).not.toContain("A".repeat(501))
175+
})
176+
177+
it("should use custom support prompts when provided", async () => {
178+
const customSupportPrompts = {
179+
ENHANCE: "Custom enhancement template: ${userInput}",
180+
}
181+
182+
await MessageEnhancer.enhanceMessage({
183+
text: "Test prompt",
184+
apiConfiguration: mockApiConfiguration,
185+
customSupportPrompts,
186+
listApiConfigMeta: mockListApiConfigMeta,
187+
providerSettingsManager: mockProviderSettingsManager,
188+
})
189+
190+
const calledPrompt = mockSingleCompletionHandler.mock.calls[0][1]
191+
expect(calledPrompt).toBe("Custom enhancement template: Test prompt")
192+
})
193+
194+
it("should handle errors gracefully", async () => {
195+
mockSingleCompletionHandler.mockRejectedValue(new Error("API error"))
196+
197+
const result = await MessageEnhancer.enhanceMessage({
198+
text: "Test",
199+
apiConfiguration: mockApiConfiguration,
200+
listApiConfigMeta: mockListApiConfigMeta,
201+
providerSettingsManager: mockProviderSettingsManager,
202+
})
203+
204+
expect(result.success).toBe(false)
205+
expect(result.error).toBe("API error")
206+
expect(result.enhancedText).toBeUndefined()
207+
})
208+
209+
it("should handle non-Error exceptions", async () => {
210+
mockSingleCompletionHandler.mockRejectedValue("String error")
211+
212+
const result = await MessageEnhancer.enhanceMessage({
213+
text: "Test",
214+
apiConfiguration: mockApiConfiguration,
215+
listApiConfigMeta: mockListApiConfigMeta,
216+
providerSettingsManager: mockProviderSettingsManager,
217+
})
218+
219+
expect(result.success).toBe(false)
220+
expect(result.error).toBe("String error")
221+
})
222+
223+
it("should fall back to default config if enhancement config is invalid", async () => {
224+
mockProviderSettingsManager.getProfile = vi.fn().mockResolvedValue({
225+
name: "Invalid Config",
226+
// Missing apiProvider
227+
})
228+
229+
await MessageEnhancer.enhanceMessage({
230+
text: "Test",
231+
apiConfiguration: mockApiConfiguration,
232+
listApiConfigMeta: mockListApiConfigMeta,
233+
enhancementApiConfigId: "config2",
234+
providerSettingsManager: mockProviderSettingsManager,
235+
})
236+
237+
// Should use the default config
238+
expect(mockSingleCompletionHandler).toHaveBeenCalledWith(mockApiConfiguration, expect.any(String))
239+
})
240+
241+
it("should handle empty task history gracefully", async () => {
242+
const result = await MessageEnhancer.enhanceMessage({
243+
text: "Test",
244+
apiConfiguration: mockApiConfiguration,
245+
listApiConfigMeta: mockListApiConfigMeta,
246+
includeTaskHistoryInEnhance: true,
247+
currentClineMessages: [],
248+
providerSettingsManager: mockProviderSettingsManager,
249+
})
250+
251+
expect(result.success).toBe(true)
252+
253+
const calledPrompt = mockSingleCompletionHandler.mock.calls[0][1]
254+
// Should not include task history section
255+
expect(calledPrompt).not.toContain("previous conversation context")
256+
})
257+
})
258+
259+
describe("captureTelemetry", () => {
260+
it("should capture telemetry when TelemetryService is available", () => {
261+
const mockTaskId = "task-123"
262+
MessageEnhancer.captureTelemetry(mockTaskId)
263+
264+
expect(TelemetryService.hasInstance).toHaveBeenCalled()
265+
expect(TelemetryService.instance.capturePromptEnhanced).toHaveBeenCalledWith(mockTaskId)
266+
})
267+
268+
it("should handle missing TelemetryService gracefully", () => {
269+
vi.mocked(TelemetryService).hasInstance = vi.fn().mockReturnValue(false)
270+
271+
// Should not throw
272+
expect(() => MessageEnhancer.captureTelemetry("task-123")).not.toThrow()
273+
})
274+
275+
it("should work without task ID", () => {
276+
MessageEnhancer.captureTelemetry()
277+
278+
expect(TelemetryService.instance.capturePromptEnhanced).toHaveBeenCalledWith(undefined)
279+
})
280+
})
281+
282+
describe("extractTaskHistory", () => {
283+
it("should filter and format messages correctly", () => {
284+
const messages: ClineMessage[] = [
285+
{ type: "ask", text: "User message 1", ts: 1000 },
286+
{ type: "say", say: "text", text: "Assistant message 1", ts: 2000 },
287+
{ type: "say", say: "reasoning", text: "Tool use", ts: 3000 },
288+
{ type: "ask", text: "", ts: 4000 }, // Empty text
289+
{ type: "say", say: "text", text: undefined, ts: 5000 }, // No text
290+
{ type: "ask", text: "User message 2", ts: 6000 },
291+
]
292+
293+
// Access private method through any type assertion for testing
294+
const history = (MessageEnhancer as any).extractTaskHistory(messages)
295+
296+
expect(history).toContain("User: User message 1")
297+
expect(history).toContain("Assistant: Assistant message 1")
298+
expect(history).toContain("User: User message 2")
299+
expect(history).not.toContain("Tool use")
300+
expect(history.split("\n").length).toBe(3) // Only 3 valid messages
301+
})
302+
})
303+
})

0 commit comments

Comments
 (0)