@aws/lsp-codewhisperer 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +19 -0
  3. package/out/client/sigv4/codewhisperer.d.ts +3 -0
  4. package/out/client/sigv4/codewhisperer.js +14 -0
  5. package/out/client/sigv4/codewhisperer.js.map +1 -0
  6. package/out/client/sigv4/service.json +346 -0
  7. package/out/client/token/bearer-token-service.json +1627 -0
  8. package/out/client/token/codewhisperer.d.ts +14 -0
  9. package/out/client/token/codewhisperer.js +23 -0
  10. package/out/client/token/codewhisperer.js.map +1 -0
  11. package/out/index.d.ts +2 -0
  12. package/out/index.js +19 -0
  13. package/out/index.js.map +1 -0
  14. package/out/language-server/auto-trigger/autoTrigger.d.ts +38 -0
  15. package/out/language-server/auto-trigger/autoTrigger.js +119 -0
  16. package/out/language-server/auto-trigger/autoTrigger.js.map +1 -0
  17. package/out/language-server/auto-trigger/autoTrigger.test.d.ts +1 -0
  18. package/out/language-server/auto-trigger/autoTrigger.test.js +82 -0
  19. package/out/language-server/auto-trigger/autoTrigger.test.js.map +1 -0
  20. package/out/language-server/auto-trigger/coefficients.json +405 -0
  21. package/out/language-server/codeWhispererSecurityScanServer.d.ts +4 -0
  22. package/out/language-server/codeWhispererSecurityScanServer.js +76 -0
  23. package/out/language-server/codeWhispererSecurityScanServer.js.map +1 -0
  24. package/out/language-server/codeWhispererServer.d.ts +7 -0
  25. package/out/language-server/codeWhispererServer.js +425 -0
  26. package/out/language-server/codeWhispererServer.js.map +1 -0
  27. package/out/language-server/codeWhispererServer.test.d.ts +1 -0
  28. package/out/language-server/codeWhispererServer.test.js +1218 -0
  29. package/out/language-server/codeWhispererServer.test.js.map +1 -0
  30. package/out/language-server/codeWhispererService.d.ts +84 -0
  31. package/out/language-server/codeWhispererService.js +168 -0
  32. package/out/language-server/codeWhispererService.js.map +1 -0
  33. package/out/language-server/dependencyGraph/commonUtil.d.ts +2 -0
  34. package/out/language-server/dependencyGraph/commonUtil.js +20 -0
  35. package/out/language-server/dependencyGraph/commonUtil.js.map +1 -0
  36. package/out/language-server/dependencyGraph/constants.d.ts +2 -0
  37. package/out/language-server/dependencyGraph/constants.js +6 -0
  38. package/out/language-server/dependencyGraph/constants.js.map +1 -0
  39. package/out/language-server/dependencyGraph/csharpDependencyGraph.d.ts +27 -0
  40. package/out/language-server/dependencyGraph/csharpDependencyGraph.js +145 -0
  41. package/out/language-server/dependencyGraph/csharpDependencyGraph.js.map +1 -0
  42. package/out/language-server/dependencyGraph/csharpDependencyGraph.test.d.ts +1 -0
  43. package/out/language-server/dependencyGraph/csharpDependencyGraph.test.js +341 -0
  44. package/out/language-server/dependencyGraph/csharpDependencyGraph.test.js.map +1 -0
  45. package/out/language-server/dependencyGraph/dependencyGraph.d.ts +125 -0
  46. package/out/language-server/dependencyGraph/dependencyGraph.js +152 -0
  47. package/out/language-server/dependencyGraph/dependencyGraph.js.map +1 -0
  48. package/out/language-server/dependencyGraph/dependencyGraphFactory.d.ts +15 -0
  49. package/out/language-server/dependencyGraph/dependencyGraphFactory.js +22 -0
  50. package/out/language-server/dependencyGraph/dependencyGraphFactory.js.map +1 -0
  51. package/out/language-server/languageDetection.d.ts +3 -0
  52. package/out/language-server/languageDetection.js +56 -0
  53. package/out/language-server/languageDetection.js.map +1 -0
  54. package/out/language-server/mergeRightUtils.d.ts +6 -0
  55. package/out/language-server/mergeRightUtils.js +37 -0
  56. package/out/language-server/mergeRightUtils.js.map +1 -0
  57. package/out/language-server/mergeRightUtils.test.d.ts +1 -0
  58. package/out/language-server/mergeRightUtils.test.js +53 -0
  59. package/out/language-server/mergeRightUtils.test.js.map +1 -0
  60. package/out/language-server/proxy-server.d.ts +2 -0
  61. package/out/language-server/proxy-server.js +35 -0
  62. package/out/language-server/proxy-server.js.map +1 -0
  63. package/out/language-server/securityScan/securityScanHandler.d.ts +17 -0
  64. package/out/language-server/securityScan/securityScanHandler.js +139 -0
  65. package/out/language-server/securityScan/securityScanHandler.js.map +1 -0
  66. package/out/language-server/securityScan/securityScanHandler.test.d.ts +1 -0
  67. package/out/language-server/securityScan/securityScanHandler.test.js +170 -0
  68. package/out/language-server/securityScan/securityScanHandler.test.js.map +1 -0
  69. package/out/language-server/securityScan/types.d.ts +49 -0
  70. package/out/language-server/securityScan/types.js +3 -0
  71. package/out/language-server/securityScan/types.js.map +1 -0
  72. package/out/language-server/session/sessionManager.d.ts +91 -0
  73. package/out/language-server/session/sessionManager.js +234 -0
  74. package/out/language-server/session/sessionManager.js.map +1 -0
  75. package/out/language-server/session/sessionManager.test.d.ts +1 -0
  76. package/out/language-server/session/sessionManager.test.js +584 -0
  77. package/out/language-server/session/sessionManager.test.js.map +1 -0
  78. package/out/language-server/telemetry/codePercentage.d.ts +17 -0
  79. package/out/language-server/telemetry/codePercentage.js +82 -0
  80. package/out/language-server/telemetry/codePercentage.js.map +1 -0
  81. package/out/language-server/telemetry/codePercentage.test.d.ts +1 -0
  82. package/out/language-server/telemetry/codePercentage.test.js +85 -0
  83. package/out/language-server/telemetry/codePercentage.test.js.map +1 -0
  84. package/out/language-server/telemetry/types.d.ts +68 -0
  85. package/out/language-server/telemetry/types.js +3 -0
  86. package/out/language-server/telemetry/types.js.map +1 -0
  87. package/out/language-server/telemetry/userTriggerDecision.test.d.ts +1 -0
  88. package/out/language-server/telemetry/userTriggerDecision.test.js +946 -0
  89. package/out/language-server/telemetry/userTriggerDecision.test.js.map +1 -0
  90. package/out/language-server/telemetry.test.d.ts +1 -0
  91. package/out/language-server/telemetry.test.js +96 -0
  92. package/out/language-server/telemetry.test.js.map +1 -0
  93. package/out/language-server/testUtils.d.ts +69 -0
  94. package/out/language-server/testUtils.js +90 -0
  95. package/out/language-server/testUtils.js.map +1 -0
  96. package/out/language-server/utils.d.ts +6 -0
  97. package/out/language-server/utils.js +31 -0
  98. package/out/language-server/utils.js.map +1 -0
  99. package/package.json +53 -0
  100. package/script/generateServiceClient.ts +242 -0
@@ -0,0 +1,1218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const testing_1 = require("@aws/language-server-runtimes/out/testing");
4
+ const assert = require("assert");
5
+ const ts_sinon_1 = require("ts-sinon");
6
+ const vscode_languageserver_1 = require("vscode-languageserver");
7
+ const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
8
+ const codeWhispererServer_1 = require("./codeWhispererServer");
9
+ const sessionManager_1 = require("./session/sessionManager");
10
+ const testUtils_1 = require("./testUtils");
11
+ describe('CodeWhisperer Server', () => {
12
+ const sandbox = ts_sinon_1.default.createSandbox();
13
+ let SESSION_IDS_LOG = [];
14
+ let sessionManager;
15
+ let sessionManagerSpy;
16
+ let generateSessionIdStub;
17
+ beforeEach(() => {
18
+ const StubSessionIdGenerator = () => {
19
+ const id = 'some-random-session-uuid-' + SESSION_IDS_LOG.length;
20
+ SESSION_IDS_LOG.push(id);
21
+ return id;
22
+ };
23
+ generateSessionIdStub = ts_sinon_1.default
24
+ .stub(sessionManager_1.CodeWhispererSession.prototype, 'generateSessionId')
25
+ .callsFake(StubSessionIdGenerator);
26
+ sessionManager = sessionManager_1.SessionManager.getInstance();
27
+ sessionManagerSpy = sandbox.spy(sessionManager);
28
+ });
29
+ afterEach(() => {
30
+ generateSessionIdStub.restore();
31
+ sessionManager_1.SessionManager.reset();
32
+ sandbox.restore();
33
+ SESSION_IDS_LOG = [];
34
+ });
35
+ describe('Recommendations', () => {
36
+ let features;
37
+ let server;
38
+ // TODO move more of the service code out of the stub and into the testable realm
39
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
40
+ // for examples on how to mock just the SDK client
41
+ let service;
42
+ beforeEach(async () => {
43
+ // Set up the server with a mock service, returning predefined recommendations
44
+ service = (0, ts_sinon_1.stubInterface)();
45
+ service.generateSuggestions.returns(Promise.resolve({
46
+ suggestions: testUtils_1.EXPECTED_SUGGESTION,
47
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
48
+ }));
49
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
50
+ // Initialize the features, but don't start server yet
51
+ features = new testing_1.TestFeatures();
52
+ // Return no specific configuration for CodeWhisperer
53
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
54
+ // Start the server and open a document
55
+ await features.start(server);
56
+ features
57
+ .openDocument(testUtils_1.SOME_FILE)
58
+ .openDocument(testUtils_1.SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID)
59
+ .openDocument(testUtils_1.SOME_UNSUPPORTED_FILE)
60
+ .openDocument(testUtils_1.SOME_FILE_WITH_EXTENSION)
61
+ .openDocument(testUtils_1.SOME_SINGLE_LINE_FILE);
62
+ });
63
+ afterEach(() => {
64
+ features.dispose();
65
+ });
66
+ it('should return recommendations', async () => {
67
+ const result = await features.doInlineCompletionWithReferences({
68
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
69
+ position: { line: 0, character: 0 },
70
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
71
+ }, vscode_languageserver_1.CancellationToken.None);
72
+ // Check the completion result
73
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT);
74
+ const expectedGenerateSuggestionsRequest = {
75
+ fileContext: {
76
+ filename: testUtils_1.SOME_FILE.uri,
77
+ programmingLanguage: { languageName: 'csharp' },
78
+ leftFileContent: '',
79
+ rightFileContent: testUtils_1.HELLO_WORLD_IN_CSHARP,
80
+ },
81
+ maxResults: 5,
82
+ };
83
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
84
+ });
85
+ it('should correctly get left and right context', async () => {
86
+ const cutOffLine = 2;
87
+ const lines = testUtils_1.HELLO_WORLD_IN_CSHARP.split('\n');
88
+ const firstTwoLines = lines.slice(0, cutOffLine).join('\n') + '\n';
89
+ const remainingLines = lines.slice(cutOffLine).join('\n');
90
+ const result = await features.doInlineCompletionWithReferences({
91
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
92
+ position: { line: cutOffLine, character: 0 },
93
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
94
+ }, vscode_languageserver_1.CancellationToken.None);
95
+ // Check the completion result
96
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT);
97
+ const expectedGenerateSuggestionsRequest = {
98
+ fileContext: {
99
+ filename: testUtils_1.SOME_FILE.uri,
100
+ programmingLanguage: { languageName: 'csharp' },
101
+ leftFileContent: firstTwoLines,
102
+ rightFileContent: remainingLines,
103
+ },
104
+ maxResults: 5,
105
+ };
106
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
107
+ });
108
+ it('should truncate left and right context', async () => {
109
+ const BIG_FILE_CONTENT = '123456789\n'.repeat(5000);
110
+ const BIG_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///big_file.cs', 'csharp', 1, BIG_FILE_CONTENT);
111
+ const cutOffLine = 2000;
112
+ features.openDocument(BIG_FILE);
113
+ await features.doInlineCompletionWithReferences({
114
+ textDocument: { uri: BIG_FILE.uri },
115
+ position: { line: cutOffLine, character: 1 },
116
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
117
+ }, vscode_languageserver_1.CancellationToken.None);
118
+ const leftContentChecker = (leftContent) => leftContent.length == codeWhispererServer_1.CONTEXT_CHARACTERS_LIMIT && leftContent.endsWith('\n1');
119
+ const rightContentChecker = (rightContent) => rightContent.length == codeWhispererServer_1.CONTEXT_CHARACTERS_LIMIT && rightContent.startsWith('234');
120
+ ts_sinon_1.default.assert.calledWith(service.generateSuggestions, ts_sinon_1.default.match.hasNested('fileContext.leftFileContent', ts_sinon_1.default.match(leftContentChecker)));
121
+ ts_sinon_1.default.assert.calledWith(service.generateSuggestions, ts_sinon_1.default.match.hasNested('fileContext.rightFileContent', ts_sinon_1.default.match(rightContentChecker)));
122
+ });
123
+ it('should return recommendations when using a different languageId casing', async () => {
124
+ const result = await features.doInlineCompletionWithReferences({
125
+ textDocument: { uri: testUtils_1.SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID.uri },
126
+ position: { line: 0, character: 0 },
127
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
128
+ }, vscode_languageserver_1.CancellationToken.None);
129
+ // Check the completion result
130
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT);
131
+ const expectedGenerateSuggestionsRequest = {
132
+ fileContext: {
133
+ filename: testUtils_1.SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID.uri,
134
+ programmingLanguage: { languageName: 'csharp' },
135
+ leftFileContent: '',
136
+ rightFileContent: testUtils_1.HELLO_WORLD_IN_CSHARP,
137
+ },
138
+ maxResults: 5,
139
+ };
140
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
141
+ });
142
+ it('should not return recommendations for a closed file', async () => {
143
+ const result = await features.doInlineCompletionWithReferences({
144
+ textDocument: { uri: testUtils_1.SOME_CLOSED_FILE.uri },
145
+ position: { line: 0, character: 0 },
146
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
147
+ }, vscode_languageserver_1.CancellationToken.None);
148
+ // Check the completion result
149
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
150
+ // Check the service was not called
151
+ ts_sinon_1.default.assert.notCalled(service.generateSuggestions);
152
+ });
153
+ it('should not return recommendations for an unsupported file type', async () => {
154
+ const result = await features.doInlineCompletionWithReferences({
155
+ textDocument: { uri: testUtils_1.SOME_UNSUPPORTED_FILE.uri },
156
+ position: { line: 0, character: 0 },
157
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
158
+ }, vscode_languageserver_1.CancellationToken.None);
159
+ // Check the completion result
160
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
161
+ // Check the service was not called
162
+ ts_sinon_1.default.assert.notCalled(service.generateSuggestions);
163
+ });
164
+ it('should return recommendations based on known extension', async () => {
165
+ const result = await features.doInlineCompletionWithReferences({
166
+ textDocument: { uri: testUtils_1.SOME_FILE_WITH_EXTENSION.uri },
167
+ position: { line: 0, character: 0 },
168
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
169
+ }, vscode_languageserver_1.CancellationToken.None);
170
+ // Check the completion result
171
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT);
172
+ const expectedGenerateSuggestionsRequest = {
173
+ fileContext: {
174
+ filename: testUtils_1.SOME_FILE_WITH_EXTENSION.uri,
175
+ programmingLanguage: { languageName: 'cpp' },
176
+ leftFileContent: '',
177
+ rightFileContent: testUtils_1.HELLO_WORLD_IN_CSHARP,
178
+ },
179
+ maxResults: 5,
180
+ };
181
+ // Check the service was called with the right parameters
182
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
183
+ });
184
+ // Merge right tests
185
+ it('should not show recommendation when the recommendation is equal to right context', async () => {
186
+ // The suggestion returned by generateSuggestions will be equal to the contents of the file
187
+ const EXPECTED_SUGGESTION = [{ itemId: 'cwspr-item-id', content: testUtils_1.HELLO_WORLD_IN_CSHARP }];
188
+ service.generateSuggestions.returns(Promise.resolve({
189
+ suggestions: EXPECTED_SUGGESTION,
190
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
191
+ }));
192
+ const result = await features.doInlineCompletionWithReferences({
193
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
194
+ position: { line: 0, character: 0 },
195
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
196
+ }, vscode_languageserver_1.CancellationToken.None);
197
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
198
+ });
199
+ it('should only show the part of the recommendation that does not overlap with the right context in multiline', async () => {
200
+ const cutOffLine = 2;
201
+ const lines = testUtils_1.HELLO_WORLD_IN_CSHARP.split('\n');
202
+ // The recommendation will be the contents of hello world starting from line 3 (static void Main)
203
+ const recommendation = lines.slice(cutOffLine).join('\n');
204
+ // We delete the static void Main line from Hello World but keep the rest in the file
205
+ const deletedLine = lines.splice(cutOffLine, 1)[0];
206
+ const finalFileContent = lines.join('\n');
207
+ const MY_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///rightContext.cs', 'csharp', 1, finalFileContent);
208
+ features.openDocument(MY_FILE);
209
+ const EXPECTED_SUGGESTION = [{ itemId: 'cwspr-item-id', content: recommendation }];
210
+ service.generateSuggestions.returns(Promise.resolve({
211
+ suggestions: EXPECTED_SUGGESTION,
212
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
213
+ }));
214
+ // Expected result is the deleted line + new line + 4 spaces
215
+ // Newline and the 4 spaces get lost when we do the `split` so we add them back to expected result
216
+ const EXPECTED_RESULT = {
217
+ sessionId: testUtils_1.EXPECTED_SESSION_ID,
218
+ items: [
219
+ {
220
+ itemId: EXPECTED_SUGGESTION[0].itemId,
221
+ insertText: deletedLine.concat('\n '),
222
+ range: undefined,
223
+ references: undefined,
224
+ },
225
+ ],
226
+ };
227
+ const result = await features.doInlineCompletionWithReferences({
228
+ textDocument: { uri: MY_FILE.uri },
229
+ position: { line: cutOffLine, character: 0 },
230
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
231
+ }, vscode_languageserver_1.CancellationToken.None);
232
+ assert.deepEqual(result, EXPECTED_RESULT);
233
+ const leftContext = lines.slice(0, cutOffLine).join('\n') + '\n';
234
+ const rightContext = lines.slice(cutOffLine).join('\n');
235
+ const expectedGenerateSuggestionsRequest = {
236
+ fileContext: {
237
+ filename: MY_FILE.uri,
238
+ programmingLanguage: { languageName: 'csharp' },
239
+ leftFileContent: leftContext,
240
+ rightFileContent: rightContext,
241
+ },
242
+ maxResults: 5,
243
+ };
244
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
245
+ });
246
+ it('should convert windows newlines to UNIX newlines in request file contents', async () => {
247
+ const cutOffLine = 2;
248
+ const lines = testUtils_1.HELLO_WORLD_WITH_WINDOWS_ENDING.split('\r\n');
249
+ const MY_WINDOWS_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///rightContext.cs', 'csharp', 1, testUtils_1.HELLO_WORLD_WITH_WINDOWS_ENDING);
250
+ features.openDocument(MY_WINDOWS_FILE);
251
+ await features.doInlineCompletionWithReferences({
252
+ textDocument: { uri: MY_WINDOWS_FILE.uri },
253
+ position: { line: cutOffLine, character: 0 },
254
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
255
+ }, vscode_languageserver_1.CancellationToken.None);
256
+ const modifiedLeftContext = lines.slice(0, cutOffLine).join('\n') + '\n';
257
+ const modifiedRightContext = lines.slice(cutOffLine).join('\n');
258
+ const expectedGenerateSuggestionsRequest = {
259
+ fileContext: {
260
+ filename: MY_WINDOWS_FILE.uri,
261
+ programmingLanguage: { languageName: 'csharp' },
262
+ leftFileContent: modifiedLeftContext,
263
+ rightFileContent: modifiedRightContext,
264
+ },
265
+ maxResults: 5,
266
+ };
267
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
268
+ });
269
+ it('should only show the part of the recommendation that does not overlap with the right context', async () => {
270
+ const EXPECTED_SUGGESTION = [{ itemId: 'cwspr-item-id', content: testUtils_1.HELLO_WORLD_LINE }];
271
+ service.generateSuggestions.returns(Promise.resolve({
272
+ suggestions: EXPECTED_SUGGESTION,
273
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
274
+ }));
275
+ const EXPECTED_RESULT = {
276
+ sessionId: testUtils_1.EXPECTED_SESSION_ID,
277
+ items: [
278
+ {
279
+ itemId: EXPECTED_SUGGESTION[0].itemId,
280
+ insertText: testUtils_1.HELLO_WORLD_LINE.substring(0, testUtils_1.SINGLE_LINE_FILE_CUTOFF_INDEX),
281
+ range: undefined,
282
+ references: undefined,
283
+ },
284
+ ],
285
+ };
286
+ const result = await features.doInlineCompletionWithReferences({
287
+ textDocument: { uri: testUtils_1.SOME_SINGLE_LINE_FILE.uri },
288
+ position: { line: 0, character: 0 },
289
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
290
+ }, vscode_languageserver_1.CancellationToken.None);
291
+ assert.deepEqual(result, EXPECTED_RESULT);
292
+ });
293
+ it('should show full recommendation when the right context does not match recommendation ', async () => {
294
+ const EXPECTED_SUGGESTION = [{ itemId: 'cwspr-item-id', content: 'Something something' }];
295
+ service.generateSuggestions.returns(Promise.resolve({
296
+ suggestions: EXPECTED_SUGGESTION,
297
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
298
+ }));
299
+ const EXPECTED_RESULT = {
300
+ sessionId: testUtils_1.EXPECTED_SESSION_ID,
301
+ items: [
302
+ {
303
+ itemId: EXPECTED_SUGGESTION[0].itemId,
304
+ insertText: EXPECTED_SUGGESTION[0].content,
305
+ range: undefined,
306
+ references: undefined,
307
+ },
308
+ ],
309
+ };
310
+ const result = await features.doInlineCompletionWithReferences({
311
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
312
+ position: { line: 0, character: 0 },
313
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
314
+ }, vscode_languageserver_1.CancellationToken.None);
315
+ assert.deepEqual(result, EXPECTED_RESULT);
316
+ });
317
+ it('should return empty recommendations list on failed request', async () => {
318
+ // Plant exception
319
+ service.generateSuggestions.returns(Promise.reject('UNEXPECTED EXCEPTION'));
320
+ const result = await features.doInlineCompletionWithReferences({
321
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
322
+ position: { line: 0, character: 0 },
323
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
324
+ }, vscode_languageserver_1.CancellationToken.None);
325
+ // Check the completion result
326
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
327
+ });
328
+ // TODO: mock http request and verify the headers are passed
329
+ // or spawn an http server and pass it as an endpoint to the sdk client,
330
+ // mock responses and verify that correct headers are receieved on the server side.
331
+ // Currently the suite just checks whether the boolean is passed to codeWhispererService
332
+ describe('Opting out of sending data to CodeWhisperer', () => {
333
+ it('should send opt-out header when the setting is disabled', async () => {
334
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ shareCodeWhispererContentWithAWS: false }));
335
+ await features.start(server);
336
+ assert(service.shareCodeWhispererContentWithAWS === false);
337
+ });
338
+ it('should not send opt-out header when the setting is enabled after startup', async () => {
339
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ shareCodeWhispererContentWithAWS: false }));
340
+ await features.start(server);
341
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ shareCodeWhispererContentWithAWS: true }));
342
+ await features.openDocument(testUtils_1.SOME_FILE).doChangeConfiguration();
343
+ assert(service.shareCodeWhispererContentWithAWS === true);
344
+ });
345
+ it('should send opt-out header if no settings are specificed', async () => {
346
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
347
+ await features.start(server);
348
+ assert(service.shareCodeWhispererContentWithAWS === false);
349
+ });
350
+ });
351
+ });
352
+ describe('Recommendations With References', () => {
353
+ let features;
354
+ let server;
355
+ // TODO move more of the service code out of the stub and into the testable realm
356
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
357
+ // for examples on how to mock just the SDK client
358
+ let service;
359
+ beforeEach(() => {
360
+ // Set up the server with a mock service, returning predefined recommendations
361
+ service = (0, ts_sinon_1.stubInterface)();
362
+ service.generateSuggestions.returns(Promise.resolve({
363
+ suggestions: testUtils_1.EXPECTED_SUGGESTION_LIST,
364
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
365
+ }));
366
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
367
+ // Initialize the features, but don't start server yet
368
+ features = new testing_1.TestFeatures();
369
+ });
370
+ afterEach(() => {
371
+ features.dispose();
372
+ });
373
+ it('should return all recommendations if no settings are specificed', async () => {
374
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
375
+ await features.start(server);
376
+ const result = await features.openDocument(testUtils_1.SOME_FILE).doInlineCompletionWithReferences({
377
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
378
+ position: { line: 0, character: 0 },
379
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
380
+ }, vscode_languageserver_1.CancellationToken.None);
381
+ // Check the completion result
382
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT_WITHOUT_REFERENCES);
383
+ });
384
+ it('should filter recommendations with references if GetConfiguration is not handled by the client', async () => {
385
+ features.lsp.workspace.getConfiguration.returns(Promise.reject(new Error('GetConfiguration failed')));
386
+ await features.start(server);
387
+ const result = await features.openDocument(testUtils_1.SOME_FILE).doInlineCompletionWithReferences({
388
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
389
+ position: { line: 0, character: 0 },
390
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
391
+ }, vscode_languageserver_1.CancellationToken.None);
392
+ // Check the completion result
393
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT_WITHOUT_REFERENCES);
394
+ });
395
+ it('should return all recommendations if settings are true', async () => {
396
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
397
+ await features.start(server);
398
+ const result = await features.openDocument(testUtils_1.SOME_FILE).doInlineCompletionWithReferences({
399
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
400
+ position: { line: 0, character: 0 },
401
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
402
+ }, vscode_languageserver_1.CancellationToken.None);
403
+ // Check the completion result
404
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT_WITH_REFERENCES);
405
+ });
406
+ it('should filter recommendations with references if no code references are allowed by settings', async () => {
407
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: false }));
408
+ await features.start(server);
409
+ const result = await features.openDocument(testUtils_1.SOME_FILE).doInlineCompletionWithReferences({
410
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
411
+ position: { line: 0, character: 0 },
412
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
413
+ }, vscode_languageserver_1.CancellationToken.None);
414
+ // Check the completion result
415
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT_WITHOUT_REFERENCES);
416
+ });
417
+ it('should filter recommendations with references if code references are disabled after startup', async () => {
418
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
419
+ await features.start(server);
420
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: false }));
421
+ const afterConfigChange = await features.openDocument(testUtils_1.SOME_FILE).doChangeConfiguration();
422
+ const result = await afterConfigChange.doInlineCompletionWithReferences({
423
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
424
+ position: { line: 0, character: 0 },
425
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
426
+ }, vscode_languageserver_1.CancellationToken.None);
427
+ // Check the completion result
428
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT_WITHOUT_REFERENCES);
429
+ });
430
+ it('should filter recommendations with references if code references are enabled after startup', async () => {
431
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: false }));
432
+ await features.start(server);
433
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
434
+ const afterConfigChange = await features.openDocument(testUtils_1.SOME_FILE).doChangeConfiguration();
435
+ const result = await afterConfigChange.doInlineCompletionWithReferences({
436
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
437
+ position: { line: 0, character: 0 },
438
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
439
+ }, vscode_languageserver_1.CancellationToken.None);
440
+ // Check the completion result
441
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT_WITH_REFERENCES);
442
+ });
443
+ it('should not show references when the right context is equal to suggestion', async () => {
444
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
445
+ await features.start(server);
446
+ const EXPECTED_SUGGESTION = [{ itemId: 'cwspr-item-id', content: testUtils_1.HELLO_WORLD_IN_CSHARP }];
447
+ service.generateSuggestions.returns(Promise.resolve({
448
+ suggestions: EXPECTED_SUGGESTION,
449
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
450
+ }));
451
+ const result = await features.openDocument(testUtils_1.SOME_FILE).doInlineCompletionWithReferences({
452
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
453
+ position: { line: 0, character: 0 },
454
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
455
+ }, vscode_languageserver_1.CancellationToken.None);
456
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
457
+ });
458
+ it('should show references and update range when there is partial overlap on right context', async () => {
459
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
460
+ await features.start(server);
461
+ const cutOffLine = 2;
462
+ const lines = testUtils_1.HELLO_WORLD_IN_CSHARP.split('\n');
463
+ // The recommendation will be the contents of hello world starting from line 3 (static void Main)
464
+ const recommendation = lines.slice(cutOffLine).join('\n');
465
+ // We delete the static void Main line from Hello World but keep the rest in the file
466
+ const deletedLine = lines.splice(cutOffLine, 1)[0];
467
+ const finalFileContent = lines.join('\n');
468
+ const MY_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///rightContext.cs', 'csharp', 1, finalFileContent);
469
+ features.openDocument(MY_FILE);
470
+ const EXPECTED_REFERENCE_WITH_LONG_RANGE = {
471
+ ...testUtils_1.EXPECTED_REFERENCE,
472
+ recommendationContentSpan: { start: 0, end: testUtils_1.HELLO_WORLD_IN_CSHARP.length },
473
+ };
474
+ const EXPECTED_SUGGESTION = [
475
+ { itemId: 'cwspr-item-id', content: recommendation, references: [EXPECTED_REFERENCE_WITH_LONG_RANGE] },
476
+ ];
477
+ const insertText = deletedLine.concat('\n ');
478
+ const EXPECTED_RESULT = {
479
+ sessionId: testUtils_1.EXPECTED_SESSION_ID,
480
+ items: [
481
+ {
482
+ itemId: EXPECTED_SUGGESTION[0].itemId,
483
+ insertText: insertText,
484
+ range: undefined,
485
+ references: [
486
+ {
487
+ licenseName: EXPECTED_REFERENCE_WITH_LONG_RANGE.licenseName,
488
+ referenceName: EXPECTED_REFERENCE_WITH_LONG_RANGE.repository,
489
+ referenceUrl: EXPECTED_REFERENCE_WITH_LONG_RANGE.url,
490
+ position: {
491
+ startCharacter: EXPECTED_REFERENCE_WITH_LONG_RANGE.recommendationContentSpan?.start,
492
+ endCharacter: insertText.length - 1,
493
+ },
494
+ },
495
+ ],
496
+ },
497
+ ],
498
+ };
499
+ service.generateSuggestions.returns(Promise.resolve({
500
+ suggestions: EXPECTED_SUGGESTION,
501
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
502
+ }));
503
+ const result = await features.openDocument(MY_FILE).doInlineCompletionWithReferences({
504
+ textDocument: { uri: MY_FILE.uri },
505
+ position: { line: cutOffLine, character: 0 },
506
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
507
+ }, vscode_languageserver_1.CancellationToken.None);
508
+ assert.deepEqual(result, EXPECTED_RESULT);
509
+ });
510
+ it('should discard reference if it references trimmed content after right-context merge', async () => {
511
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: true }));
512
+ await features.start(server);
513
+ const cutOffLine = 2;
514
+ const lines = testUtils_1.HELLO_WORLD_IN_CSHARP.split('\n');
515
+ // The recommendation will be the contents of hello world starting from line 3 (static void Main)
516
+ const recommendation = lines.slice(cutOffLine).join('\n');
517
+ // We delete the static void Main line from Hello World but keep the rest in the file
518
+ const deletedLine = lines.splice(cutOffLine, 1)[0];
519
+ const finalFileContent = lines.join('\n');
520
+ const MY_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///rightContext.cs', 'csharp', 1, finalFileContent);
521
+ features.openDocument(MY_FILE);
522
+ const insertText = deletedLine.concat('\n ');
523
+ // reference range covers portion of string that will be removed
524
+ const EXPECTED_REFERENCE_WITH_OUTER_RANGE = {
525
+ ...testUtils_1.EXPECTED_REFERENCE,
526
+ recommendationContentSpan: { start: insertText.length, end: recommendation.length },
527
+ };
528
+ const EXPECTED_SUGGESTION = [
529
+ { itemId: 'cwspr-item-id', content: recommendation, references: [EXPECTED_REFERENCE_WITH_OUTER_RANGE] },
530
+ ];
531
+ const EXPECTED_RESULT = {
532
+ sessionId: testUtils_1.EXPECTED_SESSION_ID,
533
+ items: [
534
+ {
535
+ itemId: EXPECTED_SUGGESTION[0].itemId,
536
+ insertText: insertText,
537
+ range: undefined,
538
+ references: undefined,
539
+ },
540
+ ],
541
+ };
542
+ service.generateSuggestions.returns(Promise.resolve({
543
+ suggestions: EXPECTED_SUGGESTION,
544
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
545
+ }));
546
+ const result = await features.openDocument(MY_FILE).doInlineCompletionWithReferences({
547
+ textDocument: { uri: MY_FILE.uri },
548
+ position: { line: cutOffLine, character: 0 },
549
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
550
+ }, vscode_languageserver_1.CancellationToken.None);
551
+ assert.deepEqual(result, EXPECTED_RESULT);
552
+ });
553
+ describe('With session management', () => {
554
+ it('should close session if code references are disabled and all suggestions had references', async () => {
555
+ const EXPECTED_SUGGESTION_WITH_REFERENCES = [
556
+ {
557
+ itemId: 'cwspr-item-id-1',
558
+ content: 'recommendation with reference',
559
+ references: [testUtils_1.EXPECTED_REFERENCE],
560
+ },
561
+ ];
562
+ service.generateSuggestions.returns(Promise.resolve({
563
+ suggestions: EXPECTED_SUGGESTION_WITH_REFERENCES,
564
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
565
+ }));
566
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({ includeSuggestionsWithCodeReferences: false }));
567
+ await features.start(server);
568
+ const result = await features.openDocument(testUtils_1.SOME_FILE).doInlineCompletionWithReferences({
569
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
570
+ position: { line: 0, character: 0 },
571
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
572
+ }, vscode_languageserver_1.CancellationToken.None);
573
+ // Check the completion result
574
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
575
+ // There is no active session
576
+ assert.equal(sessionManager.getActiveSession(), undefined);
577
+ assert.equal(sessionManagerSpy.createSession.callCount, 1);
578
+ assert.equal(sessionManagerSpy.closeSession.callCount, 1);
579
+ });
580
+ });
581
+ });
582
+ describe('With auto-triggers', async () => {
583
+ const AUTO_TRIGGER_POSITION = { line: 2, character: 21 };
584
+ const LEFT_FILE_CONTEXT = testUtils_1.HELLO_WORLD_IN_CSHARP.substring(0, 40);
585
+ const RIGHT_FILE_CONTEXT = testUtils_1.HELLO_WORLD_IN_CSHARP.substring(40);
586
+ let features;
587
+ let server;
588
+ // TODO move more of the service code out of the stub and into the testable realm
589
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
590
+ // for examples on how to mock just the SDK client
591
+ let service;
592
+ beforeEach(async () => {
593
+ // Set up the server with a mock service, returning predefined recommendations
594
+ service = (0, ts_sinon_1.stubInterface)();
595
+ service.generateSuggestions.returns(Promise.resolve({
596
+ suggestions: testUtils_1.EXPECTED_SUGGESTION,
597
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
598
+ }));
599
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
600
+ // Initialize the features, but don't start server yet
601
+ features = new testing_1.TestFeatures();
602
+ // Return no specific configuration for CodeWhisperer
603
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
604
+ // Start the server and open a document
605
+ await features.start(server);
606
+ features.openDocument(testUtils_1.SOME_FILE);
607
+ });
608
+ afterEach(() => {
609
+ features.dispose();
610
+ });
611
+ it('should return recommendations on an above-threshold auto-trigger position', async () => {
612
+ // Similar to test from above, this test case also depends on file contents not starting with a new line
613
+ const HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE = testUtils_1.HELLO_WORLD_IN_CSHARP.trimStart();
614
+ const SOME_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///test.cs', 'csharp', 1, HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE);
615
+ features.openDocument(SOME_FILE);
616
+ const LEFT_FILE_CONTEXT = HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE.substring(0, 40);
617
+ const RIGHT_FILE_CONTEXT = HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE.substring(40);
618
+ const result = await features.doInlineCompletionWithReferences({
619
+ textDocument: { uri: SOME_FILE.uri },
620
+ position: AUTO_TRIGGER_POSITION,
621
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
622
+ }, vscode_languageserver_1.CancellationToken.None);
623
+ // Check the completion result
624
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT);
625
+ const expectedGenerateSuggestionsRequest = {
626
+ fileContext: {
627
+ filename: SOME_FILE.uri,
628
+ programmingLanguage: { languageName: 'csharp' },
629
+ leftFileContent: LEFT_FILE_CONTEXT,
630
+ rightFileContent: RIGHT_FILE_CONTEXT,
631
+ },
632
+ maxResults: 1,
633
+ };
634
+ ts_sinon_1.default.assert.calledOnceWithExactly(service.generateSuggestions, expectedGenerateSuggestionsRequest);
635
+ });
636
+ it('shoud not return recommendations on a below-threshold auto-trigger position', async () => {
637
+ const result = await features.doInlineCompletionWithReferences({
638
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
639
+ position: { line: 0, character: 0 },
640
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
641
+ }, vscode_languageserver_1.CancellationToken.None);
642
+ // Check the completion result
643
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
644
+ });
645
+ });
646
+ describe('Log Inline Completion Session Results', () => {
647
+ const requestContext = {
648
+ maxResults: 5,
649
+ fileContext: {
650
+ filename: 'SomeFile',
651
+ programmingLanguage: { languageName: 'csharp' },
652
+ leftFileContent: 'LeftFileContent',
653
+ rightFileContent: 'RightFileContent',
654
+ },
655
+ };
656
+ const sessionData = {
657
+ startPosition: { line: 0, character: 0 },
658
+ triggerType: 'OnDemand',
659
+ language: 'csharp',
660
+ requestContext: requestContext,
661
+ };
662
+ const sessionResultData = {
663
+ sessionId: 'some-random-session-uuid-0',
664
+ completionSessionResult: {
665
+ 'cwspr-item-id': {
666
+ seen: true,
667
+ accepted: false,
668
+ discarded: false,
669
+ },
670
+ },
671
+ firstCompletionDisplayLatency: 50,
672
+ totalSessionDisplayTime: 1000,
673
+ };
674
+ let features;
675
+ let server;
676
+ // TODO move more of the service code out of the stub and into the testable realm
677
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
678
+ // for examples on how to mock just the SDK client
679
+ let service;
680
+ beforeEach(async () => {
681
+ // Set up the server with a mock service, returning predefined recommendations
682
+ service = (0, ts_sinon_1.stubInterface)();
683
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
684
+ // Initialize the features, but don't start server yet
685
+ features = new testing_1.TestFeatures();
686
+ // Start the server and open a document
687
+ await features.start(server);
688
+ features.openDocument(testUtils_1.SOME_FILE);
689
+ });
690
+ afterEach(() => {
691
+ features.dispose();
692
+ });
693
+ it('should deactivate current session when session result for current session is sent', async () => {
694
+ const manager = sessionManager_1.SessionManager.getInstance();
695
+ const session = manager.createSession(sessionData);
696
+ manager.activateSession(session);
697
+ assert.equal(session.state, 'ACTIVE');
698
+ await features.doLogInlineCompletionSessionResults(sessionResultData);
699
+ assert.equal(session.state, 'CLOSED');
700
+ });
701
+ it('should not close current session when session result for different session is sent', async () => {
702
+ const manager = sessionManager_1.SessionManager.getInstance();
703
+ const session = manager.createSession(sessionData);
704
+ manager.activateSession(session);
705
+ const session2 = manager.createSession(sessionData);
706
+ manager.activateSession(session2);
707
+ assert.equal(session.state, 'CLOSED');
708
+ assert.equal(session2.state, 'ACTIVE');
709
+ await features.doLogInlineCompletionSessionResults(sessionResultData);
710
+ assert.equal(session2.state, 'ACTIVE');
711
+ });
712
+ it('should store session result data', async () => {
713
+ const manager = sessionManager_1.SessionManager.getInstance();
714
+ const session = manager.createSession(sessionData);
715
+ manager.activateSession(session);
716
+ await features.doLogInlineCompletionSessionResults(sessionResultData);
717
+ assert.equal(session.completionSessionResult, sessionResultData.completionSessionResult);
718
+ assert.equal(session.firstCompletionDisplayLatency, sessionResultData.firstCompletionDisplayLatency);
719
+ assert.equal(session.totalSessionDisplayTime, sessionResultData.totalSessionDisplayTime);
720
+ });
721
+ it('should store session result data with only completion state provided', async () => {
722
+ const sessionResultData = {
723
+ sessionId: 'some-random-session-uuid-0',
724
+ completionSessionResult: {
725
+ 'cwspr-item-id': {
726
+ seen: true,
727
+ accepted: false,
728
+ discarded: false,
729
+ },
730
+ },
731
+ };
732
+ const manager = sessionManager_1.SessionManager.getInstance();
733
+ const session = manager.createSession(sessionData);
734
+ manager.activateSession(session);
735
+ await features.doLogInlineCompletionSessionResults(sessionResultData);
736
+ assert.equal(session.completionSessionResult, sessionResultData.completionSessionResult);
737
+ assert.equal(session.firstCompletionDisplayLatency, undefined);
738
+ assert.equal(session.totalSessionDisplayTime, undefined);
739
+ });
740
+ });
741
+ describe('Telemetry', () => {
742
+ const EXPECTED_RESPONSE_CONTEXT = {
743
+ requestId: 'cwspr-request-id',
744
+ codewhispererSessionId: 'cwspr-session-id',
745
+ };
746
+ const sessionResultData = {
747
+ sessionId: 'some-random-session-uuid-0',
748
+ completionSessionResult: {
749
+ 'cwspr-item-id': {
750
+ seen: true,
751
+ accepted: false,
752
+ discarded: false,
753
+ },
754
+ },
755
+ firstCompletionDisplayLatency: 50,
756
+ totalSessionDisplayTime: 1000,
757
+ };
758
+ let features;
759
+ let server;
760
+ // TODO move more of the service code out of the stub and into the testable realm
761
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
762
+ // for examples on how to mock just the SDK client
763
+ let service;
764
+ let clock;
765
+ beforeEach(async () => {
766
+ clock = ts_sinon_1.default.useFakeTimers({
767
+ now: 1483228800000,
768
+ });
769
+ // Set up the server with a mock service, returning predefined recommendations
770
+ service = (0, ts_sinon_1.stubInterface)();
771
+ service.generateSuggestions.returns(Promise.resolve({
772
+ suggestions: testUtils_1.EXPECTED_SUGGESTION,
773
+ responseContext: EXPECTED_RESPONSE_CONTEXT,
774
+ }));
775
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
776
+ // Initialize the features, but don't start server yet
777
+ features = new testing_1.TestFeatures();
778
+ // Return no specific configuration for CodeWhisperer
779
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
780
+ // Start the server and open a document
781
+ await features.start(server);
782
+ features.openDocument(testUtils_1.SOME_FILE);
783
+ });
784
+ afterEach(async () => {
785
+ clock.restore();
786
+ features.dispose();
787
+ });
788
+ it('should emit Success ServiceInvocation telemetry on successful response', async () => {
789
+ await features.doInlineCompletionWithReferences({
790
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
791
+ position: { line: 0, character: 0 },
792
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
793
+ }, vscode_languageserver_1.CancellationToken.None);
794
+ const expectedServiceInvocationMetric = {
795
+ name: 'codewhisperer_serviceInvocation',
796
+ result: 'Succeeded',
797
+ data: {
798
+ codewhispererRequestId: 'cwspr-request-id',
799
+ codewhispererSessionId: 'cwspr-session-id',
800
+ codewhispererLastSuggestionIndex: 0,
801
+ codewhispererCompletionType: 'Line',
802
+ codewhispererTriggerType: 'OnDemand',
803
+ codewhispererAutomatedTriggerType: undefined,
804
+ duration: 0,
805
+ codewhispererLineNumber: 0,
806
+ codewhispererCursorOffset: 0,
807
+ codewhispererLanguage: 'csharp',
808
+ credentialStartUrl: undefined,
809
+ },
810
+ };
811
+ ts_sinon_1.default.assert.calledOnceWithExactly(features.telemetry.emitMetric, expectedServiceInvocationMetric);
812
+ });
813
+ it('should emit Success ServiceInvocation telemetry on successful response with completionType block when first suggestion has new lines', async () => {
814
+ const recommendation = ['multi', 'line', ' suggestion'].join('\n');
815
+ const EXPECTED_SUGGESTIONS = [
816
+ { itemId: 'cwspr-item-id-1', content: recommendation },
817
+ { itemId: 'cwspr-item-id-2', content: recommendation },
818
+ { itemId: 'cwspr-item-id-3', content: recommendation },
819
+ ];
820
+ service.generateSuggestions.returns(Promise.resolve({
821
+ suggestions: EXPECTED_SUGGESTIONS,
822
+ responseContext: EXPECTED_RESPONSE_CONTEXT,
823
+ }));
824
+ await features.doInlineCompletionWithReferences({
825
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
826
+ position: { line: 0, character: 0 },
827
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
828
+ }, vscode_languageserver_1.CancellationToken.None);
829
+ const expectedServiceInvocationMetric = {
830
+ name: 'codewhisperer_serviceInvocation',
831
+ result: 'Succeeded',
832
+ data: {
833
+ codewhispererRequestId: 'cwspr-request-id',
834
+ codewhispererSessionId: 'cwspr-session-id',
835
+ codewhispererLastSuggestionIndex: 2,
836
+ codewhispererCompletionType: 'Block',
837
+ codewhispererTriggerType: 'OnDemand',
838
+ codewhispererAutomatedTriggerType: undefined,
839
+ duration: 0,
840
+ codewhispererLineNumber: 0,
841
+ codewhispererCursorOffset: 0,
842
+ codewhispererLanguage: 'csharp',
843
+ credentialStartUrl: undefined,
844
+ },
845
+ };
846
+ ts_sinon_1.default.assert.calledOnceWithExactly(features.telemetry.emitMetric, expectedServiceInvocationMetric);
847
+ });
848
+ it('should emit Failure ServiceInvocation telemetry on failed response', async () => {
849
+ const error = new Error('UNEXPECTED EXCEPTION');
850
+ error.name = 'TestError';
851
+ service.generateSuggestions.returns(Promise.reject(error));
852
+ await features.doInlineCompletionWithReferences({
853
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
854
+ position: { line: 0, character: 0 },
855
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
856
+ }, vscode_languageserver_1.CancellationToken.None);
857
+ const expectedServiceInvocationMetric = {
858
+ name: 'codewhisperer_serviceInvocation',
859
+ result: 'Failed',
860
+ data: {
861
+ codewhispererRequestId: undefined,
862
+ codewhispererSessionId: undefined,
863
+ codewhispererLastSuggestionIndex: -1,
864
+ codewhispererTriggerType: 'OnDemand',
865
+ codewhispererAutomatedTriggerType: undefined,
866
+ reason: 'CodeWhisperer Invocation Exception: TestError',
867
+ duration: 0,
868
+ codewhispererLineNumber: 0,
869
+ codewhispererCursorOffset: 0,
870
+ codewhispererLanguage: 'csharp',
871
+ credentialStartUrl: undefined,
872
+ },
873
+ errorData: {
874
+ reason: 'TestError',
875
+ errorCode: undefined,
876
+ httpStatusCode: undefined,
877
+ },
878
+ };
879
+ ts_sinon_1.default.assert.calledOnceWithExactly(features.telemetry.emitMetric, expectedServiceInvocationMetric);
880
+ });
881
+ it('should emit error with UnknownError reason if error name is not present', async () => {
882
+ service.generateSuggestions.returns(Promise.reject('UNEXPECTED EXCEPTION'));
883
+ await features.doInlineCompletionWithReferences({
884
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
885
+ position: { line: 0, character: 0 },
886
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
887
+ }, vscode_languageserver_1.CancellationToken.None);
888
+ const expectedServiceInvocationMetric = {
889
+ name: 'codewhisperer_serviceInvocation',
890
+ result: 'Failed',
891
+ data: {
892
+ codewhispererRequestId: undefined,
893
+ codewhispererSessionId: undefined,
894
+ codewhispererLastSuggestionIndex: -1,
895
+ codewhispererTriggerType: 'OnDemand',
896
+ codewhispererAutomatedTriggerType: undefined,
897
+ reason: 'CodeWhisperer Invocation Exception: UnknownError',
898
+ duration: 0,
899
+ codewhispererLineNumber: 0,
900
+ codewhispererCursorOffset: 0,
901
+ codewhispererLanguage: 'csharp',
902
+ credentialStartUrl: undefined,
903
+ },
904
+ errorData: {
905
+ reason: 'UnknownError',
906
+ errorCode: undefined,
907
+ httpStatusCode: undefined,
908
+ },
909
+ };
910
+ ts_sinon_1.default.assert.calledOnceWithExactly(features.telemetry.emitMetric, expectedServiceInvocationMetric);
911
+ });
912
+ it('should emit Failure ServiceInvocation telemetry with request metadata on failed response with AWSError error type', async () => {
913
+ const error = new Error('Fake Error');
914
+ error.name = 'TestAWSError';
915
+ error.code = 'TestErrorStatusCode';
916
+ error.statusCode = 500;
917
+ error.time = new Date();
918
+ error.requestId = 'failed-request-id';
919
+ service.generateSuggestions.callsFake(_request => {
920
+ clock.tick(1000);
921
+ return Promise.reject(error);
922
+ });
923
+ const getCompletionsPromise = features.doInlineCompletionWithReferences({
924
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
925
+ position: { line: 0, character: 0 },
926
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
927
+ }, vscode_languageserver_1.CancellationToken.None);
928
+ await getCompletionsPromise;
929
+ const expectedServiceInvocationMetric = {
930
+ name: 'codewhisperer_serviceInvocation',
931
+ result: 'Failed',
932
+ data: {
933
+ codewhispererRequestId: 'failed-request-id',
934
+ codewhispererSessionId: undefined,
935
+ codewhispererLastSuggestionIndex: -1,
936
+ codewhispererTriggerType: 'OnDemand',
937
+ codewhispererAutomatedTriggerType: undefined,
938
+ reason: 'CodeWhisperer Invocation Exception: TestAWSError',
939
+ duration: 1000,
940
+ codewhispererLineNumber: 0,
941
+ codewhispererCursorOffset: 0,
942
+ codewhispererLanguage: 'csharp',
943
+ credentialStartUrl: undefined,
944
+ },
945
+ errorData: {
946
+ reason: 'TestAWSError',
947
+ errorCode: 'TestErrorStatusCode',
948
+ httpStatusCode: 500,
949
+ },
950
+ };
951
+ ts_sinon_1.default.assert.calledOnceWithExactly(features.telemetry.emitMetric, expectedServiceInvocationMetric);
952
+ });
953
+ it('should emit Perceived Latency metric when session result is received', async () => {
954
+ await features.doInlineCompletionWithReferences({
955
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
956
+ position: { line: 0, character: 0 },
957
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
958
+ }, vscode_languageserver_1.CancellationToken.None);
959
+ await features.doLogInlineCompletionSessionResults(sessionResultData);
960
+ const expectedPerceivedLatencyMetric = {
961
+ name: 'codewhisperer_perceivedLatency',
962
+ data: {
963
+ codewhispererRequestId: EXPECTED_RESPONSE_CONTEXT.requestId,
964
+ codewhispererSessionId: EXPECTED_RESPONSE_CONTEXT.codewhispererSessionId,
965
+ codewhispererCompletionType: 'Line',
966
+ codewhispererTriggerType: 'OnDemand',
967
+ duration: 50,
968
+ codewhispererLanguage: 'csharp',
969
+ credentialStartUrl: undefined,
970
+ },
971
+ };
972
+ ts_sinon_1.default.assert.calledWithExactly(features.telemetry.emitMetric, expectedPerceivedLatencyMetric);
973
+ });
974
+ it('should not emit Perceived Latency metric when firstCompletionDisplayLatency is absent', async () => {
975
+ const sessionResultDataWithoutLatency = {
976
+ ...sessionResultData,
977
+ firstCompletionDisplayLatency: undefined,
978
+ };
979
+ await features.doInlineCompletionWithReferences({
980
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
981
+ position: { line: 0, character: 0 },
982
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
983
+ }, vscode_languageserver_1.CancellationToken.None);
984
+ await features.doLogInlineCompletionSessionResults(sessionResultDataWithoutLatency);
985
+ ts_sinon_1.default.assert.neverCalledWith(features.telemetry.emitMetric, ts_sinon_1.default.match.has('name', 'codewhisperer_perceivedLatency'));
986
+ });
987
+ describe('Connection metadata credentialStartUrl field', () => {
988
+ it('should attach credentialStartUrl field if available in credentialsProvider', async () => {
989
+ features.credentialsProvider.getConnectionMetadata.returns({
990
+ sso: {
991
+ startUrl: 'http://teststarturl',
992
+ },
993
+ });
994
+ await features.doInlineCompletionWithReferences({
995
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
996
+ position: { line: 0, character: 0 },
997
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
998
+ }, vscode_languageserver_1.CancellationToken.None);
999
+ assert.equal(features.telemetry.emitMetric.getCall(0).args[0].data.credentialStartUrl, 'http://teststarturl');
1000
+ });
1001
+ it('should send empty credentialStartUrl field if not available in credentialsProvider', async () => {
1002
+ features.credentialsProvider.getConnectionMetadata.returns(undefined);
1003
+ await features.doInlineCompletionWithReferences({
1004
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1005
+ position: { line: 0, character: 0 },
1006
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
1007
+ }, vscode_languageserver_1.CancellationToken.None);
1008
+ assert.equal(features.telemetry.emitMetric.getCall(0).args[0].data.credentialStartUrl, undefined);
1009
+ });
1010
+ });
1011
+ });
1012
+ describe('Recommendations session management', () => {
1013
+ const AUTO_TRIGGER_POSITION = { line: 2, character: 21 };
1014
+ let features;
1015
+ let server;
1016
+ // TODO move more of the service code out of the stub and into the testable realm
1017
+ // See: https://aws.amazon.com/blogs/developer/mocking-modular-aws-sdk-for-javascript-v3-in-unit-tests/
1018
+ // for examples on how to mock just the SDK client
1019
+ let service;
1020
+ let clock;
1021
+ beforeEach(async () => {
1022
+ clock = ts_sinon_1.default.useFakeTimers({
1023
+ now: 1483228800000,
1024
+ });
1025
+ // Set up the server with a mock service, returning predefined recommendations
1026
+ service = (0, ts_sinon_1.stubInterface)();
1027
+ service.generateSuggestions.returns(Promise.resolve({
1028
+ suggestions: testUtils_1.EXPECTED_SUGGESTION,
1029
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
1030
+ }));
1031
+ server = (0, codeWhispererServer_1.CodewhispererServerFactory)(_auth => service);
1032
+ // Initialize the features, but don't start server yet
1033
+ features = new testing_1.TestFeatures();
1034
+ // Return no specific configuration for CodeWhisperer
1035
+ features.lsp.workspace.getConfiguration.returns(Promise.resolve({}));
1036
+ // Start the server and open a document
1037
+ await features.start(server);
1038
+ features.openDocument(testUtils_1.SOME_FILE).openDocument(testUtils_1.SOME_FILE_WITH_ALT_CASED_LANGUAGE_ID);
1039
+ });
1040
+ afterEach(() => {
1041
+ clock.restore();
1042
+ features.dispose();
1043
+ });
1044
+ it('should cache new session on new request when no session exists', async () => {
1045
+ let activeSession = sessionManager.getCurrentSession();
1046
+ assert.equal(activeSession, undefined);
1047
+ await features.doInlineCompletionWithReferences({
1048
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1049
+ position: AUTO_TRIGGER_POSITION,
1050
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1051
+ }, vscode_languageserver_1.CancellationToken.None);
1052
+ // Get session after call is done
1053
+ activeSession = sessionManager.getCurrentSession();
1054
+ const expectedSessionData = {
1055
+ id: SESSION_IDS_LOG[0],
1056
+ state: 'ACTIVE',
1057
+ suggestions: [{ itemId: 'cwspr-item-id', content: 'recommendation' }],
1058
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
1059
+ codewhispererSessionId: testUtils_1.EXPECTED_RESPONSE_CONTEXT.codewhispererSessionId,
1060
+ timeToFirstRecommendation: 0,
1061
+ };
1062
+ assert(activeSession);
1063
+ ts_sinon_1.default.assert.match({
1064
+ id: activeSession.id,
1065
+ state: activeSession.state,
1066
+ suggestions: activeSession.suggestions,
1067
+ responseContext: activeSession.responseContext,
1068
+ codewhispererSessionId: activeSession.codewhispererSessionId,
1069
+ timeToFirstRecommendation: activeSession.timeToFirstRecommendation,
1070
+ }, expectedSessionData);
1071
+ });
1072
+ it('should discard inflight session on new request when cached session is in REQUESTING state on subsequent requests', async () => {
1073
+ const getCompletionsResponses = await Promise.all([
1074
+ features.doInlineCompletionWithReferences({
1075
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1076
+ position: AUTO_TRIGGER_POSITION,
1077
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1078
+ }, vscode_languageserver_1.CancellationToken.None),
1079
+ features.doInlineCompletionWithReferences({
1080
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1081
+ position: AUTO_TRIGGER_POSITION,
1082
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1083
+ }, vscode_languageserver_1.CancellationToken.None),
1084
+ features.doInlineCompletionWithReferences({
1085
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1086
+ position: AUTO_TRIGGER_POSITION,
1087
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1088
+ }, vscode_languageserver_1.CancellationToken.None),
1089
+ ]);
1090
+ // 3 requests were processed by server, but only first should return results
1091
+ const EXPECTED_COMPLETION_RESPONSES = [
1092
+ { sessionId: '', items: [] },
1093
+ { sessionId: '', items: [] },
1094
+ { sessionId: SESSION_IDS_LOG[2], items: testUtils_1.EXPECTED_RESULT.items }, // Last session wins
1095
+ ];
1096
+ // Only last request must return completion items
1097
+ assert.deepEqual(getCompletionsResponses, EXPECTED_COMPLETION_RESPONSES);
1098
+ assert.equal(sessionManagerSpy.createSession.callCount, 3);
1099
+ // Get session after call is done
1100
+ const activeSession = sessionManager.getCurrentSession();
1101
+ // 3 sessions were created
1102
+ assert.equal(SESSION_IDS_LOG.length, 3);
1103
+ // Last session is ACTIVE stored in manager correctly
1104
+ const expectedSessionData = {
1105
+ id: SESSION_IDS_LOG[2],
1106
+ state: 'ACTIVE',
1107
+ suggestions: [{ itemId: 'cwspr-item-id', content: 'recommendation' }],
1108
+ };
1109
+ assert(activeSession);
1110
+ ts_sinon_1.default.assert.match({
1111
+ id: activeSession.id,
1112
+ state: activeSession.state,
1113
+ suggestions: activeSession.suggestions,
1114
+ }, expectedSessionData);
1115
+ });
1116
+ it('should record all sessions that were created in session log', async () => {
1117
+ // Start 3 session, 2 will be cancelled inflight
1118
+ await Promise.all([
1119
+ features.doInlineCompletionWithReferences({
1120
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1121
+ position: AUTO_TRIGGER_POSITION,
1122
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1123
+ }, vscode_languageserver_1.CancellationToken.None),
1124
+ features.doInlineCompletionWithReferences({
1125
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1126
+ position: AUTO_TRIGGER_POSITION,
1127
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1128
+ }, vscode_languageserver_1.CancellationToken.None),
1129
+ features.doInlineCompletionWithReferences({
1130
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1131
+ position: AUTO_TRIGGER_POSITION,
1132
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1133
+ }, vscode_languageserver_1.CancellationToken.None),
1134
+ ]);
1135
+ // Get session after call is done
1136
+ const firstActiveSession = sessionManager.getCurrentSession();
1137
+ // Do another request, which will close last ACTIVE session
1138
+ await features.doInlineCompletionWithReferences({
1139
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1140
+ position: AUTO_TRIGGER_POSITION,
1141
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1142
+ }, vscode_languageserver_1.CancellationToken.None);
1143
+ assert.equal(sessionManagerSpy.createSession.callCount, 4);
1144
+ assert.equal(sessionManager.getSessionsLog().length, 4);
1145
+ const latestSession = sessionManager.getCurrentSession();
1146
+ assert.equal(latestSession, sessionManager.getActiveSession());
1147
+ assert.equal(latestSession, sessionManager.getCurrentSession());
1148
+ // All sessions
1149
+ assert.equal(firstActiveSession?.id, 'some-random-session-uuid-2');
1150
+ });
1151
+ it('should close new session on new request when service returns empty list', async () => {
1152
+ service.generateSuggestions.returns(Promise.resolve({
1153
+ suggestions: [],
1154
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
1155
+ }));
1156
+ const activeSession = sessionManager.getCurrentSession();
1157
+ assert.equal(activeSession, undefined);
1158
+ await features.doInlineCompletionWithReferences({
1159
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1160
+ position: AUTO_TRIGGER_POSITION,
1161
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Automatic },
1162
+ }, vscode_languageserver_1.CancellationToken.None);
1163
+ // Get session after call is done
1164
+ const currentSession = sessionManager.getCurrentSession();
1165
+ assert(currentSession);
1166
+ assert.equal(currentSession?.state, 'CLOSED');
1167
+ ts_sinon_1.default.assert.calledOnceWithExactly(sessionManagerSpy.closeSession, currentSession);
1168
+ });
1169
+ it('Manual completion invocation should close previous session', async () => {
1170
+ const TRIGGER_KIND = vscode_languageserver_1.InlineCompletionTriggerKind.Invoked;
1171
+ const result = await features.doInlineCompletionWithReferences({
1172
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1173
+ position: { line: 0, character: 0 },
1174
+ // Manual trigger kind
1175
+ context: { triggerKind: TRIGGER_KIND },
1176
+ }, vscode_languageserver_1.CancellationToken.None);
1177
+ assert.deepEqual(result, testUtils_1.EXPECTED_RESULT);
1178
+ const firstSession = sessionManager.getActiveSession();
1179
+ // There is ACTIVE session
1180
+ assert(firstSession);
1181
+ assert.equal(sessionManager.getCurrentSession(), firstSession);
1182
+ assert.equal(firstSession.state, 'ACTIVE');
1183
+ const secondResult = await features.doInlineCompletionWithReferences({
1184
+ textDocument: { uri: testUtils_1.SOME_FILE.uri },
1185
+ position: { line: 0, character: 0 },
1186
+ context: { triggerKind: TRIGGER_KIND },
1187
+ }, vscode_languageserver_1.CancellationToken.None);
1188
+ assert.deepEqual(secondResult, { ...testUtils_1.EXPECTED_RESULT, sessionId: SESSION_IDS_LOG[1] });
1189
+ ts_sinon_1.default.assert.called(sessionManagerSpy.closeCurrentSession);
1190
+ });
1191
+ it('should discard inflight session if merge right recommendations resulted in list of empty strings', async () => {
1192
+ // The suggestion returned by generateSuggestions will be equal to the contents of the file
1193
+ // This test fails when the file starts with a new line, probably due to the way we handle right context merge
1194
+ // So let's use hello world without the newline in the beginning
1195
+ const HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE = testUtils_1.HELLO_WORLD_IN_CSHARP.trimStart();
1196
+ const SOME_FILE = vscode_languageserver_textdocument_1.TextDocument.create('file:///test.cs', 'csharp', 1, HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE);
1197
+ features.openDocument(SOME_FILE);
1198
+ const EXPECTED_SUGGESTION = [
1199
+ { itemId: 'cwspr-item-id', content: HELLO_WORLD_IN_CSHARP_WITHOUT_NEWLINE },
1200
+ ];
1201
+ service.generateSuggestions.returns(Promise.resolve({
1202
+ suggestions: EXPECTED_SUGGESTION,
1203
+ responseContext: testUtils_1.EXPECTED_RESPONSE_CONTEXT,
1204
+ }));
1205
+ const result = await features.doInlineCompletionWithReferences({
1206
+ textDocument: { uri: SOME_FILE.uri },
1207
+ position: { line: 0, character: 0 },
1208
+ context: { triggerKind: vscode_languageserver_1.InlineCompletionTriggerKind.Invoked },
1209
+ }, vscode_languageserver_1.CancellationToken.None);
1210
+ assert.deepEqual(result, testUtils_1.EMPTY_RESULT);
1211
+ const session = sessionManager.getCurrentSession();
1212
+ assert(session);
1213
+ assert(session.state, 'CLOSED');
1214
+ ts_sinon_1.default.assert.calledOnce(sessionManagerSpy.closeSession);
1215
+ });
1216
+ });
1217
+ });
1218
+ //# sourceMappingURL=codeWhispererServer.test.js.map