Path: blob/main/extensions/copilot/src/extension/test/vscode-node/session.test.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import * as sinon from 'sinon';7import { authentication, AuthenticationGetSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, AuthenticationWwwAuthenticateRequest, ConfigurationScope, ConfigurationTarget, workspace, WorkspaceConfiguration } from 'vscode';8import { GITHUB_SCOPE_ALIGNED, GITHUB_SCOPE_READ_USER, GITHUB_SCOPE_USER_EMAIL } from '../../../platform/authentication/common/authentication';9import { getAlignedSession, getAnyAuthSession } from '../../../platform/authentication/vscode-node/session';10import { AuthProviderId, Config, ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';11import { DefaultsOnlyConfigurationService } from '../../../platform/configuration/common/defaultsOnlyConfigurationService';12import { InMemoryConfigurationService } from '../../../platform/configuration/test/common/inMemoryConfigurationService';13import { ITelemetryUserConfig, TelemetryUserConfigImpl } from '../../../platform/telemetry/common/telemetry';14import { SyncDescriptor } from '../../../util/vs/platform/instantiation/common/descriptors';15import { createExtensionTestingServices } from './services';1617suite('Session tests', function () {18const testingServiceCollection = createExtensionTestingServices();19testingServiceCollection.define(ITelemetryUserConfig, new SyncDescriptor(TelemetryUserConfigImpl, ['test', false]));20const accessor = testingServiceCollection.createTestingAccessor();2122let sandbox: sinon.SinonSandbox;23let getSessionStub: sinon.SinonStub<[providerId: string, scopeListOrRequest: readonly string[] | AuthenticationWwwAuthenticateRequest, options?: AuthenticationGetSessionOptions | undefined], Thenable<AuthenticationSession | undefined>>;24let getAccountsStub: sinon.SinonStub<[providerId: string], Thenable<readonly AuthenticationSessionAccountInformation[]>>;25let configurationStub: sinon.SinonStub<[section?: string | undefined, scope?: ConfigurationScope | null | undefined], WorkspaceConfiguration>;2627function seedSessions(sessions: AuthenticationSession[]) {2829getAccountsStub.resolves(sessions.map(session => session.account));3031const sessionsByScope = new Map<string, AuthenticationSession[]>();32for (const session of sessions) {33const scopeKey = session.scopes.join(' ');34const sessionsWithScope = sessionsByScope.get(scopeKey) || [];35sessionsWithScope.push(session);36sessionsByScope.set(scopeKey, sessionsWithScope);37}3839getSessionStub.callsFake((_providerId: string, scopeListOrRequest: readonly string[] | AuthenticationWwwAuthenticateRequest, options?: AuthenticationGetSessionOptions | undefined) => {40const scopes = scopeListOrRequest as readonly string[];41let sessionsWithScope = sessionsByScope.get(scopes.join(' '));42if (sessionsWithScope) {43if (options?.account) {44sessionsWithScope = sessionsWithScope.filter(session => session.account.label === options.account?.label);45}46return Promise.resolve(sessionsWithScope[0]);47}4849return Promise.resolve(undefined);50});5152}5354setup(() => {55sandbox = sinon.createSandbox();56getSessionStub = sandbox.stub(authentication, 'getSession');57// default to no session58getSessionStub.resolves(undefined);59getAccountsStub = sandbox.stub(authentication, 'getAccounts');60// default to no accounts61getAccountsStub.resolves([]);62configurationStub = sandbox.stub(workspace, 'getConfiguration');63configurationStub.returns(new class implements WorkspaceConfiguration {64private _data = new Map<string, any>();65get<T>(section: string): T | undefined;66get<T>(section: string, defaultValue: T): T;67get<T>(section: unknown, defaultValue?: unknown): T | undefined {68return this._data.get(section as string) ?? defaultValue;69}70has(section: string): boolean {71return !!this._data.get(section);72}73inspect<T>(section: string): undefined {74return undefined;75}76update(section: string, value: any, configurationTarget?: boolean | ConfigurationTarget | null | undefined, overrideInLanguage?: boolean | undefined): Thenable<void> {77this._data.set(section, value);78return Promise.resolve();79}80});81});8283teardown(() => {84sandbox.restore();85});8687suite('getAnyAuthSession', () => {88test('should return a session with ["user:email"] scope if it is available', async () => {89const scopes = GITHUB_SCOPE_USER_EMAIL;90const sessionId = 'session-id-1';9192seedSessions([{ id: sessionId, scopes, accessToken: 'token', account: { id: 'account', label: 'account-label' } }]);9394const result = await getAnyAuthSession(accessor.get(IConfigurationService));9596assert.strictEqual((result as AuthenticationSession)?.id, sessionId);97});9899test('should return a session with ["read:user"] scope if it is available', async () => {100const scopes = GITHUB_SCOPE_READ_USER;101const sessionId = 'session-id-1';102103seedSessions([{ id: sessionId, scopes, accessToken: 'token', account: { id: 'account', label: 'account-label' } }]);104105const result = await getAnyAuthSession(accessor.get(IConfigurationService));106107assert.strictEqual((result as AuthenticationSession)?.id, sessionId);108});109110test('should return a session with aligned scopes if it is available', async () => {111const scopes = GITHUB_SCOPE_ALIGNED;112const sessionId = 'session-id-1';113114seedSessions([{ id: sessionId, scopes, accessToken: 'token', account: { id: 'account', label: 'account-label' } }]);115116const result = await getAnyAuthSession(accessor.get(IConfigurationService));117118assert.strictEqual((result as AuthenticationSession)?.id, sessionId);119});120121test('should return a session with the ["user:email"] scope over ["read:user"] if it is available', async () => {122const newSessionId = 'new-session-id-1';123const oldSessionId = 'old-session-id-2';124125seedSessions([126{127id: oldSessionId,128accessToken: 'old-token',129scopes: GITHUB_SCOPE_READ_USER,130account: { id: 'account', label: 'account-label' },131},132{133id: newSessionId,134accessToken: 'new-token',135scopes: GITHUB_SCOPE_USER_EMAIL,136account: { id: 'account', label: 'account-label' },137}138]);139140const result = await getAnyAuthSession(accessor.get(IConfigurationService));141142assert.strictEqual((result as AuthenticationSession)?.id, newSessionId);143});144145test('should return a session with the aligned scopes if it is available', async () => {146const newSessionId = 'new-session-id-1';147const oldSessionId = 'old-session-id-2';148const alignedSessionId = 'aligned-session-id-3';149150seedSessions([151{152id: alignedSessionId,153accessToken: 'aligned-token',154scopes: GITHUB_SCOPE_ALIGNED,155account: { id: 'account', label: 'account-label' },156},157{158id: oldSessionId,159accessToken: 'old-token',160scopes: GITHUB_SCOPE_READ_USER,161account: { id: 'account', label: 'account-label' },162},163{164id: newSessionId,165accessToken: 'new-token',166scopes: GITHUB_SCOPE_USER_EMAIL,167account: { id: 'account', label: 'account-label' },168},169]);170171const result = await getAnyAuthSession(accessor.get(IConfigurationService));172173assert.strictEqual((result as AuthenticationSession)?.id, alignedSessionId);174});175176test('should return undefined if there are no pre-existing sessions', async () => {177const alignedScopeSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_ALIGNED, sinon.match.any);178const userEmailSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_USER_EMAIL, sinon.match.any);179const readUserScopeSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_READ_USER, sinon.match.any);180181const result = await getAnyAuthSession(accessor.get(IConfigurationService));182183assert.strictEqual(result, undefined);184assert.strictEqual(alignedScopeSessionStub.calledOnce, false);185// Only call the user:email scope one since we have no accounts186assert.strictEqual(userEmailSessionStub.calledOnce, true);187assert.strictEqual(readUserScopeSessionStub.calledOnce, false);188});189190test('should use the github-enterprise provider if configured', async () => {191const configurationService = new InMemoryConfigurationService(192new DefaultsOnlyConfigurationService(),193new Map<Config<any>, unknown>([194[ConfigKey.Shared.AuthProvider, AuthProviderId.GitHubEnterprise]195]),196undefined197);198199const gheSessionId = 'ghe-session-id-1';200201getAccountsStub.resolves([{ id: 'account', label: 'ghe-session-label' }]);202const gheSessionStub = getSessionStub.withArgs('github-enterprise', GITHUB_SCOPE_READ_USER, sinon.match.any);203gheSessionStub.resolves({204id: gheSessionId,205accessToken: 'new-token',206scopes: GITHUB_SCOPE_READ_USER,207account: { id: 'account', label: 'ghe-session-label' },208});209const ghSessionStub = getSessionStub.withArgs('github', GITHUB_SCOPE_READ_USER, sinon.match.any);210211const result = await getAnyAuthSession(configurationService);212213assert.strictEqual((result as AuthenticationSession)?.id, gheSessionId);214assert.strictEqual(gheSessionStub.calledOnce, true);215assert.strictEqual(ghSessionStub.notCalled, true);216});217});218219suite('getAlignedSession', () => {220test('should return a session with more permissive scopes if there is one', async () => {221const alignedSessionId = 'session-id';222223seedSessions([224{225id: alignedSessionId,226accessToken: 'token',227scopes: GITHUB_SCOPE_ALIGNED,228account: { id: 'account', label: 'account-label' },229}230]);231232const result = await getAlignedSession(accessor.get(IConfigurationService), { silent: true });233234assert.strictEqual((result as AuthenticationSession)?.id, alignedSessionId);235});236237test('should return undefined if there is no session with permissive scopes', async () => {238const alignedSessionId = 'session-id';239240seedSessions([241{242id: alignedSessionId,243accessToken: 'token',244scopes: GITHUB_SCOPE_USER_EMAIL,245account: { id: 'account', label: 'account-label' },246}247]);248249const result = await getAlignedSession(accessor.get(IConfigurationService), { silent: true });250assert.strictEqual(result, undefined);251});252});253});254255256