Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/test/vscode-node/session.test.ts
13399 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 assert from 'assert';
7
import * as sinon from 'sinon';
8
import { authentication, AuthenticationGetSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, AuthenticationWwwAuthenticateRequest, ConfigurationScope, ConfigurationTarget, workspace, WorkspaceConfiguration } from 'vscode';
9
import { GITHUB_SCOPE_ALIGNED, GITHUB_SCOPE_READ_USER, GITHUB_SCOPE_USER_EMAIL } from '../../../platform/authentication/common/authentication';
10
import { getAlignedSession, getAnyAuthSession } from '../../../platform/authentication/vscode-node/session';
11
import { AuthProviderId, Config, ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
12
import { DefaultsOnlyConfigurationService } from '../../../platform/configuration/common/defaultsOnlyConfigurationService';
13
import { InMemoryConfigurationService } from '../../../platform/configuration/test/common/inMemoryConfigurationService';
14
import { ITelemetryUserConfig, TelemetryUserConfigImpl } from '../../../platform/telemetry/common/telemetry';
15
import { SyncDescriptor } from '../../../util/vs/platform/instantiation/common/descriptors';
16
import { createExtensionTestingServices } from './services';
17
18
suite('Session tests', function () {
19
const testingServiceCollection = createExtensionTestingServices();
20
testingServiceCollection.define(ITelemetryUserConfig, new SyncDescriptor(TelemetryUserConfigImpl, ['test', false]));
21
const accessor = testingServiceCollection.createTestingAccessor();
22
23
let sandbox: sinon.SinonSandbox;
24
let getSessionStub: sinon.SinonStub<[providerId: string, scopeListOrRequest: readonly string[] | AuthenticationWwwAuthenticateRequest, options?: AuthenticationGetSessionOptions | undefined], Thenable<AuthenticationSession | undefined>>;
25
let getAccountsStub: sinon.SinonStub<[providerId: string], Thenable<readonly AuthenticationSessionAccountInformation[]>>;
26
let configurationStub: sinon.SinonStub<[section?: string | undefined, scope?: ConfigurationScope | null | undefined], WorkspaceConfiguration>;
27
28
function seedSessions(sessions: AuthenticationSession[]) {
29
30
getAccountsStub.resolves(sessions.map(session => session.account));
31
32
const sessionsByScope = new Map<string, AuthenticationSession[]>();
33
for (const session of sessions) {
34
const scopeKey = session.scopes.join(' ');
35
const sessionsWithScope = sessionsByScope.get(scopeKey) || [];
36
sessionsWithScope.push(session);
37
sessionsByScope.set(scopeKey, sessionsWithScope);
38
}
39
40
getSessionStub.callsFake((_providerId: string, scopeListOrRequest: readonly string[] | AuthenticationWwwAuthenticateRequest, options?: AuthenticationGetSessionOptions | undefined) => {
41
const scopes = scopeListOrRequest as readonly string[];
42
let sessionsWithScope = sessionsByScope.get(scopes.join(' '));
43
if (sessionsWithScope) {
44
if (options?.account) {
45
sessionsWithScope = sessionsWithScope.filter(session => session.account.label === options.account?.label);
46
}
47
return Promise.resolve(sessionsWithScope[0]);
48
}
49
50
return Promise.resolve(undefined);
51
});
52
53
}
54
55
setup(() => {
56
sandbox = sinon.createSandbox();
57
getSessionStub = sandbox.stub(authentication, 'getSession');
58
// default to no session
59
getSessionStub.resolves(undefined);
60
getAccountsStub = sandbox.stub(authentication, 'getAccounts');
61
// default to no accounts
62
getAccountsStub.resolves([]);
63
configurationStub = sandbox.stub(workspace, 'getConfiguration');
64
configurationStub.returns(new class implements WorkspaceConfiguration {
65
private _data = new Map<string, any>();
66
get<T>(section: string): T | undefined;
67
get<T>(section: string, defaultValue: T): T;
68
get<T>(section: unknown, defaultValue?: unknown): T | undefined {
69
return this._data.get(section as string) ?? defaultValue;
70
}
71
has(section: string): boolean {
72
return !!this._data.get(section);
73
}
74
inspect<T>(section: string): undefined {
75
return undefined;
76
}
77
update(section: string, value: any, configurationTarget?: boolean | ConfigurationTarget | null | undefined, overrideInLanguage?: boolean | undefined): Thenable<void> {
78
this._data.set(section, value);
79
return Promise.resolve();
80
}
81
});
82
});
83
84
teardown(() => {
85
sandbox.restore();
86
});
87
88
suite('getAnyAuthSession', () => {
89
test('should return a session with ["user:email"] scope if it is available', async () => {
90
const scopes = GITHUB_SCOPE_USER_EMAIL;
91
const sessionId = 'session-id-1';
92
93
seedSessions([{ id: sessionId, scopes, accessToken: 'token', account: { id: 'account', label: 'account-label' } }]);
94
95
const result = await getAnyAuthSession(accessor.get(IConfigurationService));
96
97
assert.strictEqual((result as AuthenticationSession)?.id, sessionId);
98
});
99
100
test('should return a session with ["read:user"] scope if it is available', async () => {
101
const scopes = GITHUB_SCOPE_READ_USER;
102
const sessionId = 'session-id-1';
103
104
seedSessions([{ id: sessionId, scopes, accessToken: 'token', account: { id: 'account', label: 'account-label' } }]);
105
106
const result = await getAnyAuthSession(accessor.get(IConfigurationService));
107
108
assert.strictEqual((result as AuthenticationSession)?.id, sessionId);
109
});
110
111
test('should return a session with aligned scopes if it is available', async () => {
112
const scopes = GITHUB_SCOPE_ALIGNED;
113
const sessionId = 'session-id-1';
114
115
seedSessions([{ id: sessionId, scopes, accessToken: 'token', account: { id: 'account', label: 'account-label' } }]);
116
117
const result = await getAnyAuthSession(accessor.get(IConfigurationService));
118
119
assert.strictEqual((result as AuthenticationSession)?.id, sessionId);
120
});
121
122
test('should return a session with the ["user:email"] scope over ["read:user"] if it is available', async () => {
123
const newSessionId = 'new-session-id-1';
124
const oldSessionId = 'old-session-id-2';
125
126
seedSessions([
127
{
128
id: oldSessionId,
129
accessToken: 'old-token',
130
scopes: GITHUB_SCOPE_READ_USER,
131
account: { id: 'account', label: 'account-label' },
132
},
133
{
134
id: newSessionId,
135
accessToken: 'new-token',
136
scopes: GITHUB_SCOPE_USER_EMAIL,
137
account: { id: 'account', label: 'account-label' },
138
}
139
]);
140
141
const result = await getAnyAuthSession(accessor.get(IConfigurationService));
142
143
assert.strictEqual((result as AuthenticationSession)?.id, newSessionId);
144
});
145
146
test('should return a session with the aligned scopes if it is available', async () => {
147
const newSessionId = 'new-session-id-1';
148
const oldSessionId = 'old-session-id-2';
149
const alignedSessionId = 'aligned-session-id-3';
150
151
seedSessions([
152
{
153
id: alignedSessionId,
154
accessToken: 'aligned-token',
155
scopes: GITHUB_SCOPE_ALIGNED,
156
account: { id: 'account', label: 'account-label' },
157
},
158
{
159
id: oldSessionId,
160
accessToken: 'old-token',
161
scopes: GITHUB_SCOPE_READ_USER,
162
account: { id: 'account', label: 'account-label' },
163
},
164
{
165
id: newSessionId,
166
accessToken: 'new-token',
167
scopes: GITHUB_SCOPE_USER_EMAIL,
168
account: { id: 'account', label: 'account-label' },
169
},
170
]);
171
172
const result = await getAnyAuthSession(accessor.get(IConfigurationService));
173
174
assert.strictEqual((result as AuthenticationSession)?.id, alignedSessionId);
175
});
176
177
test('should return undefined if there are no pre-existing sessions', async () => {
178
const alignedScopeSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_ALIGNED, sinon.match.any);
179
const userEmailSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_USER_EMAIL, sinon.match.any);
180
const readUserScopeSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_READ_USER, sinon.match.any);
181
182
const result = await getAnyAuthSession(accessor.get(IConfigurationService));
183
184
assert.strictEqual(result, undefined);
185
assert.strictEqual(alignedScopeSessionStub.calledOnce, false);
186
// Only call the user:email scope one since we have no accounts
187
assert.strictEqual(userEmailSessionStub.calledOnce, true);
188
assert.strictEqual(readUserScopeSessionStub.calledOnce, false);
189
});
190
191
test('should use the github-enterprise provider if configured', async () => {
192
const configurationService = new InMemoryConfigurationService(
193
new DefaultsOnlyConfigurationService(),
194
new Map<Config<any>, unknown>([
195
[ConfigKey.Shared.AuthProvider, AuthProviderId.GitHubEnterprise]
196
]),
197
undefined
198
);
199
200
const gheSessionId = 'ghe-session-id-1';
201
202
getAccountsStub.resolves([{ id: 'account', label: 'ghe-session-label' }]);
203
const gheSessionStub = getSessionStub.withArgs('github-enterprise', GITHUB_SCOPE_READ_USER, sinon.match.any);
204
gheSessionStub.resolves({
205
id: gheSessionId,
206
accessToken: 'new-token',
207
scopes: GITHUB_SCOPE_READ_USER,
208
account: { id: 'account', label: 'ghe-session-label' },
209
});
210
const ghSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_READ_USER, sinon.match.any);
211
212
const result = await getAnyAuthSession(configurationService);
213
214
assert.strictEqual((result as AuthenticationSession)?.id, gheSessionId);
215
assert.strictEqual(gheSessionStub.calledOnce, true);
216
assert.strictEqual(ghSessionStub.notCalled, true);
217
});
218
});
219
220
suite('getAlignedSession', () => {
221
test('should return a session with more permissive scopes if there is one', async () => {
222
const alignedSessionId = 'session-id';
223
224
seedSessions([
225
{
226
id: alignedSessionId,
227
accessToken: 'token',
228
scopes: GITHUB_SCOPE_ALIGNED,
229
account: { id: 'account', label: 'account-label' },
230
}
231
]);
232
233
const result = await getAlignedSession(accessor.get(IConfigurationService), { silent: true });
234
235
assert.strictEqual((result as AuthenticationSession)?.id, alignedSessionId);
236
});
237
238
test('should return undefined if there is no session with permissive scopes', async () => {
239
const alignedSessionId = 'session-id';
240
241
seedSessions([
242
{
243
id: alignedSessionId,
244
accessToken: 'token',
245
scopes: GITHUB_SCOPE_USER_EMAIL,
246
account: { id: 'account', label: 'account-label' },
247
}
248
]);
249
250
const result = await getAlignedSession(accessor.get(IConfigurationService), { silent: true });
251
assert.strictEqual(result, undefined);
252
});
253
});
254
});
255
256