Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chronicle/vscode-node/test/sessionStoreTracker.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { describe, expect, it } from 'vitest';
7
import { GenAiAttr } from '../../../../platform/otel/common/genAiAttributes';
8
import type { ICompletedSpanData } from '../../../../platform/otel/common/otelService';
9
import { extractFilePath, extractToolArgs } from '../../common/sessionStoreTracking';
10
11
/**
12
* These tests verify the span data processing logic used by SessionStoreTracker.
13
*
14
* The tests focus on:
15
* 1. Tool argument extraction from OTel span attributes using the real extractToolArgs helper
16
* 2. File path extraction using the real extractFilePath helper
17
*
18
* Note: Full integration tests of SessionStoreTracker require mocking multiple
19
* services (ISessionStore, IOTelService, IChatSessionService, etc.) and are
20
* covered by manual testing and telemetry validation.
21
*/
22
23
// Create a minimal mock span for testing
24
function makeSpan(overrides: Partial<ICompletedSpanData> = {}): ICompletedSpanData {
25
return {
26
name: 'test',
27
traceId: 'trace-1',
28
spanId: 'span-1',
29
startTime: 0,
30
endTime: 1,
31
attributes: {},
32
events: [],
33
status: { code: 0 },
34
...overrides,
35
};
36
}
37
38
describe('SessionStoreTracker span processing', () => {
39
describe('tool argument extraction from OTel attributes', () => {
40
it('uses gen_ai.tool.call.arguments attribute (not gen_ai.tool.input)', () => {
41
// This test documents the fix for using the correct OTel attribute
42
const span = makeSpan({
43
attributes: {
44
// The correct attribute that OTel uses
45
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({
46
filePath: '/src/file.ts',
47
content: 'test',
48
}),
49
// This was incorrectly used before - should be ignored
50
'gen_ai.tool.input': JSON.stringify({ wrong: 'data' }),
51
},
52
});
53
54
const args = extractToolArgs(span);
55
56
expect(args).toEqual({
57
filePath: '/src/file.ts',
58
content: 'test',
59
});
60
// Verify we're not reading from the wrong attribute
61
expect(args).not.toHaveProperty('wrong');
62
});
63
64
it('returns empty object when attribute is missing', () => {
65
const span = makeSpan({ attributes: {} });
66
expect(extractToolArgs(span)).toEqual({});
67
});
68
69
it('returns empty object for malformed JSON', () => {
70
const span = makeSpan({
71
attributes: {
72
[GenAiAttr.TOOL_CALL_ARGUMENTS]: 'not valid json {',
73
},
74
});
75
expect(extractToolArgs(span)).toEqual({});
76
});
77
78
it('returns empty object for non-string attribute', () => {
79
const span = makeSpan({
80
attributes: {
81
[GenAiAttr.TOOL_CALL_ARGUMENTS]: 12345 as unknown as string,
82
},
83
});
84
expect(extractToolArgs(span)).toEqual({});
85
});
86
});
87
88
describe('file path extraction pipeline', () => {
89
// These tests verify the full pipeline: span -> extractToolArgs -> extractFilePath
90
91
it('extracts file from replace_string_in_file span', () => {
92
const span = makeSpan({
93
attributes: {
94
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({
95
filePath: '/workspace/src/utils.ts',
96
oldString: 'old',
97
newString: 'new',
98
}),
99
},
100
});
101
102
const args = extractToolArgs(span);
103
const filePath = extractFilePath('replace_string_in_file', args);
104
105
expect(filePath).toBe('/workspace/src/utils.ts');
106
});
107
108
it('extracts file from create_file span', () => {
109
const span = makeSpan({
110
attributes: {
111
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({
112
filePath: '/new/module.ts',
113
content: 'export {}',
114
}),
115
},
116
});
117
118
const args = extractToolArgs(span);
119
const filePath = extractFilePath('create_file', args);
120
121
expect(filePath).toBe('/new/module.ts');
122
});
123
124
it('extracts file from apply_patch span using input field', () => {
125
const patchInput = '*** Begin Patch\n*** Update File: /lib/helpers.ts\n@@export\n-old\n+new\n*** End Patch';
126
const span = makeSpan({
127
attributes: {
128
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({ input: patchInput }),
129
},
130
});
131
132
const args = extractToolArgs(span);
133
const filePath = extractFilePath('apply_patch', args);
134
135
expect(filePath).toBe('/lib/helpers.ts');
136
});
137
138
it('extracts file from multi_replace_string_in_file span', () => {
139
const span = makeSpan({
140
attributes: {
141
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({
142
explanation: 'fix imports',
143
replacements: [
144
{ filePath: '/src/a.ts', oldString: 'x', newString: 'y' },
145
{ filePath: '/src/b.ts', oldString: 'x', newString: 'y' },
146
],
147
}),
148
},
149
});
150
151
const args = extractToolArgs(span);
152
const filePath = extractFilePath('multi_replace_string_in_file', args);
153
154
// extractFilePath returns first file from replacements array
155
expect(filePath).toBe('/src/a.ts');
156
});
157
158
it('returns undefined for non-file tools', () => {
159
const span = makeSpan({
160
attributes: {
161
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({ command: 'ls -la' }),
162
},
163
});
164
165
const args = extractToolArgs(span);
166
const filePath = extractFilePath('run_in_terminal', args);
167
168
expect(filePath).toBeUndefined();
169
});
170
171
it('returns undefined when args are missing filePath', () => {
172
const span = makeSpan({
173
attributes: {
174
[GenAiAttr.TOOL_CALL_ARGUMENTS]: JSON.stringify({ content: 'no path' }),
175
},
176
});
177
178
const args = extractToolArgs(span);
179
const filePath = extractFilePath('create_file', args);
180
181
expect(filePath).toBeUndefined();
182
});
183
});
184
});
185
186