Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/test/copilotCloudSessionsProvider.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, vi } from 'vitest';
7
import * as vscode from 'vscode';
8
import { IGitService } from '../../../../platform/git/common/gitService';
9
import { PullRequestSearchItem, SessionInfo } from '../../../../platform/github/common/githubAPI';
10
import { TestLogService } from '../../../../platform/testing/common/testLogService';
11
import { mock } from '../../../../util/common/test/simpleMock';
12
import { ChatResponseMarkdownPart, ChatResponseTurn2 } from '../../../../vscodeTypes';
13
import { ChatSessionContentBuilder } from '../copilotCloudSessionContentBuilder';
14
import { normalizeInitialSessionOptions, parseSessionLogChunksSafely } from '../copilotCloudSessionsProvider';
15
16
vi.mock('vscode', async () => {
17
const actual = await import('../../../../vscodeTypes');
18
return {
19
...actual,
20
workspace: {
21
workspaceFolders: [],
22
},
23
};
24
});
25
26
class RecordingLogService extends TestLogService {
27
override readonly trace = vi.fn();
28
override readonly warn = vi.fn();
29
override readonly error = vi.fn();
30
}
31
32
class TestGitService extends mock<IGitService>() {
33
declare readonly _serviceBrand: undefined;
34
override activeRepository = { get: () => undefined } as IGitService['activeRepository'];
35
override initialize = vi.fn(async () => { });
36
override repositories = [];
37
}
38
39
function createPullRequest(): PullRequestSearchItem {
40
return {
41
id: 'pr-1',
42
number: 1,
43
title: 'Test PR',
44
state: 'OPEN',
45
url: 'https://example.com/pr/1',
46
createdAt: '2026-03-27T00:00:00Z',
47
updatedAt: '2026-03-27T00:00:00Z',
48
author: { login: 'octocat' },
49
repository: {
50
owner: { login: 'microsoft' },
51
name: 'vscode',
52
},
53
additions: 1,
54
deletions: 0,
55
files: { totalCount: 1 },
56
fullDatabaseId: 1,
57
headRefOid: 'abc123',
58
headRefName: 'copilot/test-branch',
59
baseRefName: 'main',
60
body: 'Body',
61
};
62
}
63
64
function createSession(state: SessionInfo['state'] = 'completed'): SessionInfo {
65
return {
66
id: 'session-1',
67
name: 'Cloud session',
68
user_id: 1,
69
agent_id: 1,
70
logs: '',
71
logs_blob_id: 'blob-1',
72
state,
73
owner_id: 1,
74
repo_id: 1,
75
resource_type: 'pull_request',
76
resource_id: 1,
77
last_updated_at: '2026-03-27T00:00:00Z',
78
created_at: '2026-03-27T00:00:00Z',
79
completed_at: '2026-03-27T00:00:00Z',
80
event_type: 'pull_request',
81
workflow_run_id: 1,
82
premium_requests: 0,
83
error: null,
84
resource_global_id: 'global-1',
85
};
86
}
87
88
describe('copilotCloudSessionsProvider helpers', () => {
89
it('coerces object-shaped initialSessionOptions into option entries', () => {
90
const logService = new RecordingLogService();
91
const sessionResource = vscode.Uri.parse('copilot-cloud-agent:/1');
92
93
const result = normalizeInitialSessionOptions({
94
models: { id: 'gpt-4.1', name: 'GPT-4.1' },
95
repositories: 'microsoft/vscode',
96
}, logService, sessionResource);
97
98
expect(result).toEqual([
99
{ optionId: 'models', value: { id: 'gpt-4.1', name: 'GPT-4.1' } },
100
{ optionId: 'repositories', value: 'microsoft/vscode' },
101
]);
102
expect(logService.warn).toHaveBeenCalledWith(expect.stringContaining('Coerced object-shaped initialSessionOptions'));
103
});
104
105
it('ignores unsupported initialSessionOptions payloads and logs a warning', () => {
106
const logService = new RecordingLogService();
107
108
const result = normalizeInitialSessionOptions({
109
models: { foo: 'bar' },
110
}, logService);
111
112
expect(result).toEqual([]);
113
expect(logService.warn).toHaveBeenCalledWith(expect.stringContaining('Ignoring unsupported initialSessionOptions'));
114
});
115
116
it('logs parse failures when streamed log content is malformed', () => {
117
const logService = new RecordingLogService();
118
119
const result = parseSessionLogChunksSafely('data: {not-json}', logService, () => {
120
throw new SyntaxError('Unexpected token');
121
});
122
123
expect(result).toEqual([]);
124
expect(logService.error).toHaveBeenCalledWith(expect.any(SyntaxError), expect.stringContaining('Failed to parse streamed log content'));
125
});
126
});
127
128
describe('ChatSessionContentBuilder', () => {
129
it('ignores malformed tool_calls payloads instead of throwing', async () => {
130
const builder = new ChatSessionContentBuilder('copilot-cloud-agent', new TestGitService());
131
const logs = [
132
'data: {"choices":[{"finish_reason":"stop","delta":{"role":"assistant","content":"Cloud reply","tool_calls":{"id":"not-an-array"}}}],"created":0,"id":"chunk-1","usage":{"completion_tokens":0,"prompt_tokens":0,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":0},"model":"test-model","object":"chat.completion.chunk"}',
133
].join('\n');
134
135
const history = await builder.buildSessionHistory(
136
Promise.resolve('Continue in cloud'),
137
[createSession()],
138
createPullRequest(),
139
async () => logs,
140
Promise.resolve([]),
141
);
142
143
expect(history).toHaveLength(2);
144
const responseTurn = history[1];
145
expect(responseTurn).toBeInstanceOf(ChatResponseTurn2);
146
if (!(responseTurn instanceof ChatResponseTurn2)) {
147
throw new Error('Expected a response turn.');
148
}
149
150
expect(responseTurn.response).toHaveLength(1);
151
expect(responseTurn.response[0]).toBeInstanceOf(ChatResponseMarkdownPart);
152
if (!(responseTurn.response[0] instanceof ChatResponseMarkdownPart)) {
153
throw new Error('Expected markdown response content.');
154
}
155
156
expect(responseTurn.response[0].value.value).toBe('Cloud reply');
157
});
158
});
159
160