Skip to content

Commit d90f7be

Browse files
committed
Add unit tests for truncation logic
1 parent be6fef8 commit d90f7be

File tree

2 files changed

+372
-1
lines changed
  • packages
    • amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest
    • core/src/codewhispererChat/controllers/chat/chatRequest

2 files changed

+372
-1
lines changed
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
import assert from 'assert'
2+
import { ChatTriggerType, TriggerPayload, triggerPayloadToChatRequest } from 'aws-core-vscode/codewhispererChat'
3+
import { filePathSizeLimit } from '../../../../../../../core/dist/src/codewhispererChat/controllers/chat/chatRequest/converter'
4+
5+
describe('triggerPayloadToChatRequest', () => {
6+
const mockBasicPayload: TriggerPayload = {
7+
message: 'test message',
8+
filePath: 'test/path.ts',
9+
fileText: 'console.log("hello")',
10+
fileLanguage: 'typescript',
11+
additionalContents: [],
12+
relevantTextDocuments: [],
13+
useRelevantDocuments: false,
14+
customization: { arn: 'test:arn' },
15+
trigger: ChatTriggerType.ChatMessage,
16+
contextLengths: {
17+
truncatedUserInputContextLength: 0,
18+
truncatedCurrentFileContextLength: 0,
19+
truncatedWorkspaceContextLength: 0,
20+
truncatedAdditionalContextLengths: {
21+
promptContextLength: 0,
22+
ruleContextLength: 0,
23+
fileContextLength: 0,
24+
},
25+
additionalContextLengths: {
26+
fileContextLength: 0,
27+
promptContextLength: 0,
28+
ruleContextLength: 0,
29+
},
30+
workspaceContextLength: 0,
31+
userInputContextLength: 0,
32+
currentFileContextLength: 0,
33+
},
34+
context: [],
35+
documentReferences: [],
36+
query: undefined,
37+
codeSelection: undefined,
38+
matchPolicy: undefined,
39+
codeQuery: undefined,
40+
userIntent: undefined,
41+
}
42+
43+
it('should convert basic trigger payload to chat request', () => {
44+
const result = triggerPayloadToChatRequest(mockBasicPayload)
45+
46+
assert.notEqual(result, undefined)
47+
assert.strictEqual(result.conversationState.currentMessage?.userInputMessage?.content, 'test message')
48+
assert.strictEqual(result.conversationState.chatTriggerType, 'MANUAL')
49+
assert.strictEqual(result.conversationState.customizationArn, 'test:arn')
50+
})
51+
52+
it('should handle empty file path', () => {
53+
const emptyFilePathPayload = {
54+
...mockBasicPayload,
55+
filePath: '',
56+
}
57+
58+
const result = triggerPayloadToChatRequest(emptyFilePathPayload)
59+
60+
assert.strictEqual(
61+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document,
62+
undefined
63+
)
64+
})
65+
66+
it('should filter out empty additional contents', () => {
67+
const payloadWithEmptyContents: TriggerPayload = {
68+
...mockBasicPayload,
69+
additionalContents: [
70+
{ name: 'prompt1', description: 'prompt1', relativePath: 'path1', type: 'prompt', innerContext: '' },
71+
{
72+
name: 'prompt2',
73+
description: 'prompt2',
74+
relativePath: 'path2',
75+
type: 'prompt',
76+
innerContext: 'valid content',
77+
},
78+
],
79+
}
80+
81+
const result = triggerPayloadToChatRequest(payloadWithEmptyContents)
82+
83+
assert.strictEqual(
84+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext
85+
?.length,
86+
1
87+
)
88+
assert.strictEqual(
89+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext.additionalContext?.[0]
90+
.innerContext,
91+
'valid content'
92+
)
93+
})
94+
95+
it('should handle unsupported programming language', () => {
96+
const unsupportedLanguagePayload = {
97+
...mockBasicPayload,
98+
fileLanguage: 'unsupported',
99+
}
100+
101+
const result = triggerPayloadToChatRequest(unsupportedLanguagePayload)
102+
103+
assert.strictEqual(
104+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document
105+
?.programmingLanguage,
106+
undefined
107+
)
108+
})
109+
110+
it('should truncate file path if it exceeds limit', () => {
111+
const longFilePath = 'a'.repeat(5000)
112+
const longFilePathPayload = {
113+
...mockBasicPayload,
114+
filePath: longFilePath,
115+
}
116+
117+
const result = triggerPayloadToChatRequest(longFilePathPayload)
118+
119+
assert.strictEqual(
120+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document
121+
?.relativeFilePath?.length,
122+
filePathSizeLimit
123+
)
124+
})
125+
})
126+
127+
describe('Context Priority Truncation Tests', () => {
128+
const createLargeString = (size: number, prefix: string = '') => prefix + 'x'.repeat(size - prefix.length)
129+
130+
const createBaseTriggerPayload = (): TriggerPayload => ({
131+
message: '',
132+
fileText: '',
133+
filePath: 'test.ts',
134+
fileLanguage: 'typescript',
135+
trigger: ChatTriggerType.ChatMessage,
136+
customization: { arn: '' },
137+
relevantTextDocuments: [],
138+
additionalContents: [],
139+
useRelevantDocuments: true,
140+
contextLengths: {
141+
truncatedUserInputContextLength: 0,
142+
truncatedCurrentFileContextLength: 0,
143+
truncatedWorkspaceContextLength: 0,
144+
truncatedAdditionalContextLengths: {
145+
promptContextLength: 0,
146+
ruleContextLength: 0,
147+
fileContextLength: 0,
148+
},
149+
additionalContextLengths: {
150+
fileContextLength: 0,
151+
promptContextLength: 0,
152+
ruleContextLength: 0,
153+
},
154+
workspaceContextLength: 0,
155+
userInputContextLength: 0,
156+
currentFileContextLength: 0,
157+
},
158+
query: undefined,
159+
codeSelection: undefined,
160+
matchPolicy: undefined,
161+
codeQuery: undefined,
162+
userIntent: undefined,
163+
context: [],
164+
documentReferences: [],
165+
})
166+
167+
it('should preserve Type A (user input) over all other types when size exceeds limit', () => {
168+
const payload = createBaseTriggerPayload()
169+
const userInputSize = 60_000
170+
const promptSize = 30_000
171+
const currentFileSize = 20_000
172+
173+
payload.message = createLargeString(userInputSize, 'userInput-')
174+
payload.additionalContents = [
175+
{
176+
name: 'prompt1',
177+
description: 'prompt1',
178+
relativePath: 'path1',
179+
type: 'prompt',
180+
innerContext: createLargeString(promptSize, 'prompt-'),
181+
},
182+
]
183+
payload.fileText = createLargeString(currentFileSize, 'currentFile-')
184+
185+
const result = triggerPayloadToChatRequest(payload)
186+
187+
// User input should be preserved completely
188+
assert.strictEqual(result.conversationState.currentMessage?.userInputMessage?.content?.length, userInputSize)
189+
190+
// Other contexts should be truncated
191+
assert.ok(
192+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.[0]
193+
.innerContext?.length! < promptSize
194+
)
195+
assert.ok(
196+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document
197+
?.text?.length! < currentFileSize
198+
)
199+
})
200+
201+
it('should preserve Type B1(prompts) over lower priority contexts when size exceeds limit', () => {
202+
const payload = createBaseTriggerPayload()
203+
const promptSize = 50_000
204+
const currentFileSize = 40_000
205+
const ruleSize = 30_000
206+
207+
payload.additionalContents = [
208+
{
209+
name: 'prompt',
210+
description: 'prompt',
211+
relativePath: 'path1',
212+
type: 'prompt',
213+
innerContext: createLargeString(promptSize, 'prompt-'),
214+
},
215+
{
216+
name: 'rule',
217+
description: 'rule',
218+
relativePath: 'path2',
219+
type: 'rule',
220+
innerContext: createLargeString(ruleSize, 'rule-'),
221+
},
222+
]
223+
payload.fileText = createLargeString(currentFileSize, 'currentFile-')
224+
225+
const result = triggerPayloadToChatRequest(payload)
226+
227+
// Prompt context should be preserved more than others
228+
const promptContext =
229+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
230+
(c) => c?.name === 'prompt'
231+
)?.innerContext
232+
const ruleContext =
233+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
234+
(c) => c?.name === 'rule'
235+
)?.innerContext
236+
237+
assert.ok(promptContext!.length > ruleContext!.length)
238+
assert.ok(
239+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document
240+
?.text?.length! < currentFileSize
241+
)
242+
})
243+
244+
it('should preserve Type C (current file) over B1(rules), B2(files), and B3(workspace)', () => {
245+
const payload = createBaseTriggerPayload()
246+
const currentFileSize = 40_000
247+
const ruleSize = 30_000
248+
const fileSize = 20_000
249+
const workspaceSize = 10_000
250+
251+
payload.fileText = createLargeString(currentFileSize, 'currentFile-')
252+
payload.additionalContents = [
253+
{
254+
name: 'rule',
255+
description: 'rule',
256+
relativePath: 'path1',
257+
type: 'rule',
258+
innerContext: createLargeString(ruleSize, 'rule-'),
259+
},
260+
{
261+
name: 'file',
262+
description: 'file',
263+
relativePath: 'path2',
264+
type: 'file',
265+
innerContext: createLargeString(fileSize, 'file-'),
266+
},
267+
]
268+
payload.relevantTextDocuments = [
269+
{
270+
relativeFilePath: 'workspace.ts',
271+
text: createLargeString(workspaceSize, 'workspace-'),
272+
startLine: -1,
273+
endLine: -1,
274+
},
275+
]
276+
277+
const result = triggerPayloadToChatRequest(payload)
278+
279+
const currentFileLength =
280+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document
281+
?.text?.length!
282+
const ruleContext =
283+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
284+
(c) => c.name === 'rule'
285+
)?.innerContext
286+
const fileContext =
287+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
288+
(c) => c.name === 'file'
289+
)?.innerContext
290+
const workspaceContext =
291+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState
292+
?.relevantDocuments?.[0].text
293+
294+
assert.ok(currentFileLength > ruleContext!.length)
295+
assert.ok(ruleContext!.length > fileContext!.length)
296+
assert.ok(fileContext!.length > workspaceContext!.length)
297+
})
298+
299+
it('should preserve priority order when all context types are present', () => {
300+
const payload = createBaseTriggerPayload()
301+
const userInputSize = 30_000
302+
const promptSize = 25_000
303+
const currentFileSize = 20_000
304+
const ruleSize = 15_000
305+
const fileSize = 10_000
306+
const workspaceSize = 5_000
307+
308+
payload.message = createLargeString(userInputSize, 'userInput-')
309+
payload.additionalContents = [
310+
{
311+
name: 'prompt',
312+
description: 'prompt',
313+
relativePath: 'path1',
314+
type: 'prompt',
315+
innerContext: createLargeString(promptSize, 'prompt-'),
316+
},
317+
{
318+
name: 'rule',
319+
description: 'rule',
320+
relativePath: 'path2',
321+
type: 'rule',
322+
innerContext: createLargeString(ruleSize, 'rule-'),
323+
},
324+
{
325+
name: 'file',
326+
description: 'file',
327+
relativePath: 'path3',
328+
type: 'file',
329+
innerContext: createLargeString(fileSize, 'file-'),
330+
},
331+
]
332+
payload.fileText = createLargeString(currentFileSize, 'currentFile-')
333+
payload.relevantTextDocuments = [
334+
{
335+
relativeFilePath: 'workspace.ts',
336+
text: createLargeString(workspaceSize, 'workspace-'),
337+
startLine: -1,
338+
endLine: -1,
339+
},
340+
]
341+
342+
const result = triggerPayloadToChatRequest(payload)
343+
344+
const userInputLength = result.conversationState.currentMessage?.userInputMessage?.content?.length!
345+
const promptContext =
346+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
347+
(c) => c.name === 'prompt'
348+
)?.innerContext
349+
const currentFileLength =
350+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document
351+
?.text?.length!
352+
const ruleContext =
353+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
354+
(c) => c.name === 'rule'
355+
)?.innerContext
356+
const fileContext =
357+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.additionalContext?.find(
358+
(c) => c.name === 'file'
359+
)?.innerContext
360+
const workspaceContext =
361+
result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState
362+
?.relevantDocuments?.[0].text
363+
364+
// Verify priority ordering
365+
assert.ok(userInputLength >= promptContext!.length)
366+
assert.ok(promptContext!.length >= currentFileLength)
367+
assert.ok(currentFileLength >= ruleContext!.length)
368+
assert.ok(ruleContext!.length >= fileContext!.length)
369+
assert.ok(fileContext!.length >= workspaceContext!.length)
370+
})
371+
})

packages/core/src/codewhispererChat/controllers/chat/chatRequest/converter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const supportedLanguagesList = [
2828
'sql',
2929
]
3030

31-
const filePathSizeLimit = 4_000
31+
export const filePathSizeLimit = 4_000
3232

3333
export function triggerPayloadToChatRequest(triggerPayload: TriggerPayload): { conversationState: ConversationState } {
3434
// Flexible truncation logic

0 commit comments

Comments
 (0)