Path: blob/main/src/vs/workbench/services/authentication/test/browser/authenticationQueryServiceMocks.ts
3296 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 { Emitter } from '../../../../../base/common/event.js';6import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';7import { AuthenticationSession, AuthenticationSessionAccount, IAuthenticationProvider, IAuthenticationService, IAuthenticationExtensionsService } from '../../common/authentication.js';8import { IAuthenticationUsageService } from '../../browser/authenticationUsageService.js';9import { IAuthenticationMcpUsageService } from '../../browser/authenticationMcpUsageService.js';10import { IAuthenticationAccessService } from '../../browser/authenticationAccessService.js';11import { IAuthenticationMcpAccessService } from '../../browser/authenticationMcpAccessService.js';12import { IAuthenticationMcpService } from '../../browser/authenticationMcpService.js';1314/**15* Helper function to create a mock authentication provider16*/17export function createProvider(overrides: Partial<IAuthenticationProvider> = {}): IAuthenticationProvider {18return {19id: 'test-provider',20label: 'Test Provider',21supportsMultipleAccounts: true,22createSession: () => Promise.resolve(createSession()),23removeSession: () => Promise.resolve(),24getSessions: () => Promise.resolve([]),25onDidChangeSessions: new Emitter<any>().event,26...overrides27};28}2930/**31* Helper function to create a mock authentication session32*/33export function createSession(): AuthenticationSession {34return {35id: 'test-session',36accessToken: 'test-token',37account: { id: 'test-account', label: 'Test Account' },38scopes: ['read', 'write'],39idToken: undefined40};41}4243/**44* Interface for tracking method calls in mock services45*/46interface MethodCall {47method: string;48args: any[];49timestamp: number;50}5152/**53* Base class for test services with common functionality and call tracking54*/55export abstract class BaseTestService extends Disposable {56protected readonly data = new Map<string, any>();57private readonly _methodCalls: MethodCall[] = [];5859protected getKey(...parts: string[]): string {60return parts.join('::');61}6263/**64* Track a method call for verification in tests65*/66protected trackCall(method: string, ...args: any[]): void {67this._methodCalls.push({68method,69args: [...args],70timestamp: Date.now()71});72}7374/**75* Get all method calls for verification76*/77getMethodCalls(): readonly MethodCall[] {78return [...this._methodCalls];79}8081/**82* Get calls for a specific method83*/84getCallsFor(method: string): readonly MethodCall[] {85return this._methodCalls.filter(call => call.method === method);86}8788/**89* Clear method call history90*/91clearCallHistory(): void {92this._methodCalls.length = 0;93}9495/**96* Get the last call for a specific method97*/98getLastCallFor(method: string): MethodCall | undefined {99const calls = this.getCallsFor(method);100return calls[calls.length - 1];101}102}103104/**105* Test implementation that actually stores and retrieves data106*/107export class TestUsageService extends BaseTestService implements IAuthenticationUsageService {108declare readonly _serviceBrand: undefined;109110readAccountUsages(providerId: string, accountName: string): any[] {111this.trackCall('readAccountUsages', providerId, accountName);112return this.data.get(this.getKey(providerId, accountName)) || [];113}114115addAccountUsage(providerId: string, accountName: string, scopes: readonly string[], extensionId: string, extensionName: string): void {116this.trackCall('addAccountUsage', providerId, accountName, scopes, extensionId, extensionName);117const key = this.getKey(providerId, accountName);118const usages = this.data.get(key) || [];119usages.push({ extensionId, extensionName, scopes: [...scopes], lastUsed: Date.now() });120this.data.set(key, usages);121}122123removeAccountUsage(providerId: string, accountName: string): void {124this.trackCall('removeAccountUsage', providerId, accountName);125this.data.delete(this.getKey(providerId, accountName));126}127128// Stub implementations for missing methods129async initializeExtensionUsageCache(): Promise<void> { }130async extensionUsesAuth(extensionId: string): Promise<boolean> { return false; }131}132133export class TestMcpUsageService extends BaseTestService implements IAuthenticationMcpUsageService {134declare readonly _serviceBrand: undefined;135136readAccountUsages(providerId: string, accountName: string): any[] {137this.trackCall('readAccountUsages', providerId, accountName);138return this.data.get(this.getKey(providerId, accountName)) || [];139}140141addAccountUsage(providerId: string, accountName: string, scopes: readonly string[], mcpServerId: string, mcpServerName: string): void {142this.trackCall('addAccountUsage', providerId, accountName, scopes, mcpServerId, mcpServerName);143const key = this.getKey(providerId, accountName);144const usages = this.data.get(key) || [];145usages.push({ mcpServerId, mcpServerName, scopes: [...scopes], lastUsed: Date.now() });146this.data.set(key, usages);147}148149removeAccountUsage(providerId: string, accountName: string): void {150this.trackCall('removeAccountUsage', providerId, accountName);151this.data.delete(this.getKey(providerId, accountName));152}153154// Stub implementations for missing methods155async initializeUsageCache(): Promise<void> { }156async hasUsedAuth(mcpServerId: string): Promise<boolean> { return false; }157}158159export class TestAccessService extends BaseTestService implements IAuthenticationAccessService {160declare readonly _serviceBrand: undefined;161private readonly _onDidChangeExtensionSessionAccess = this._register(new Emitter<any>());162onDidChangeExtensionSessionAccess = this._onDidChangeExtensionSessionAccess.event;163164isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined {165this.trackCall('isAccessAllowed', providerId, accountName, extensionId);166const extensions = this.data.get(this.getKey(providerId, accountName)) || [];167const extension = extensions.find((e: any) => e.id === extensionId);168return extension?.allowed;169}170171readAllowedExtensions(providerId: string, accountName: string): any[] {172this.trackCall('readAllowedExtensions', providerId, accountName);173return this.data.get(this.getKey(providerId, accountName)) || [];174}175176updateAllowedExtensions(providerId: string, accountName: string, extensions: any[]): void {177this.trackCall('updateAllowedExtensions', providerId, accountName, extensions);178const key = this.getKey(providerId, accountName);179const existing = this.data.get(key) || [];180181// Merge with existing data, updating or adding extensions182const merged = [...existing];183for (const ext of extensions) {184const existingIndex = merged.findIndex(e => e.id === ext.id);185if (existingIndex >= 0) {186merged[existingIndex] = ext;187} else {188merged.push(ext);189}190}191192this.data.set(key, merged);193this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName });194}195196removeAllowedExtensions(providerId: string, accountName: string): void {197this.trackCall('removeAllowedExtensions', providerId, accountName);198this.data.delete(this.getKey(providerId, accountName));199}200}201202export class TestMcpAccessService extends BaseTestService implements IAuthenticationMcpAccessService {203declare readonly _serviceBrand: undefined;204private readonly _onDidChangeMcpSessionAccess = this._register(new Emitter<any>());205onDidChangeMcpSessionAccess = this._onDidChangeMcpSessionAccess.event;206207isAccessAllowed(providerId: string, accountName: string, mcpServerId: string): boolean | undefined {208this.trackCall('isAccessAllowed', providerId, accountName, mcpServerId);209const servers = this.data.get(this.getKey(providerId, accountName)) || [];210const server = servers.find((s: any) => s.id === mcpServerId);211return server?.allowed;212}213214readAllowedMcpServers(providerId: string, accountName: string): any[] {215this.trackCall('readAllowedMcpServers', providerId, accountName);216return this.data.get(this.getKey(providerId, accountName)) || [];217}218219updateAllowedMcpServers(providerId: string, accountName: string, mcpServers: any[]): void {220this.trackCall('updateAllowedMcpServers', providerId, accountName, mcpServers);221const key = this.getKey(providerId, accountName);222const existing = this.data.get(key) || [];223224// Merge with existing data, updating or adding MCP servers225const merged = [...existing];226for (const server of mcpServers) {227const existingIndex = merged.findIndex(s => s.id === server.id);228if (existingIndex >= 0) {229merged[existingIndex] = server;230} else {231merged.push(server);232}233}234235this.data.set(key, merged);236this._onDidChangeMcpSessionAccess.fire({ providerId, accountName });237}238239removeAllowedMcpServers(providerId: string, accountName: string): void {240this.trackCall('removeAllowedMcpServers', providerId, accountName);241this.data.delete(this.getKey(providerId, accountName));242this._onDidChangeMcpSessionAccess.fire({ providerId, accountName });243}244}245246export class TestPreferencesService extends BaseTestService {247private readonly _onDidChangeAccountPreference = this._register(new Emitter<any>());248onDidChangeAccountPreference = this._onDidChangeAccountPreference.event;249250getAccountPreference(clientId: string, providerId: string): string | undefined {251return this.data.get(this.getKey(clientId, providerId));252}253254updateAccountPreference(clientId: string, providerId: string, account: any): void {255this.data.set(this.getKey(clientId, providerId), account.label);256}257258removeAccountPreference(clientId: string, providerId: string): void {259this.data.delete(this.getKey(clientId, providerId));260}261}262263export class TestExtensionsService extends TestPreferencesService implements IAuthenticationExtensionsService {264declare readonly _serviceBrand: undefined;265266// Stub implementations for methods we don't test267updateSessionPreference(): void { }268getSessionPreference(): string | undefined { return undefined; }269removeSessionPreference(): void { }270selectSession(): Promise<any> { return Promise.resolve(createSession()); }271requestSessionAccess(): void { }272requestNewSession(): Promise<void> { return Promise.resolve(); }273updateNewSessionRequests(): void { }274}275276export class TestMcpService extends TestPreferencesService implements IAuthenticationMcpService {277declare readonly _serviceBrand: undefined;278279// Stub implementations for methods we don't test280updateSessionPreference(): void { }281getSessionPreference(): string | undefined { return undefined; }282removeSessionPreference(): void { }283selectSession(): Promise<any> { return Promise.resolve(createSession()); }284requestSessionAccess(): void { }285requestNewSession(): Promise<void> { return Promise.resolve(); }286}287288/**289* Minimal authentication service mock that only implements what we need290*/291export class TestAuthenticationService extends BaseTestService implements IAuthenticationService {292declare readonly _serviceBrand: undefined;293294private readonly _onDidChangeSessions = this._register(new Emitter<any>());295private readonly _onDidRegisterAuthenticationProvider = this._register(new Emitter<any>());296private readonly _onDidUnregisterAuthenticationProvider = this._register(new Emitter<any>());297private readonly _onDidChangeDeclaredProviders = this._register(new Emitter<void>());298299onDidChangeSessions = this._onDidChangeSessions.event;300onDidRegisterAuthenticationProvider = this._onDidRegisterAuthenticationProvider.event;301onDidUnregisterAuthenticationProvider = this._onDidUnregisterAuthenticationProvider.event;302onDidChangeDeclaredProviders = this._onDidChangeDeclaredProviders.event;303304private readonly accountsMap = new Map<string, AuthenticationSessionAccount[]>();305306registerAuthenticationProvider(id: string, provider: IAuthenticationProvider): void {307this.data.set(id, provider);308this._onDidRegisterAuthenticationProvider.fire({ id, label: provider.label });309}310311getProviderIds(): string[] {312return Array.from(this.data.keys());313}314315isAuthenticationProviderRegistered(id: string): boolean {316return this.data.has(id);317}318319getProvider(id: string): IAuthenticationProvider {320return this.data.get(id)!;321}322323addAccounts(providerId: string, accounts: AuthenticationSessionAccount[]): void {324this.accountsMap.set(providerId, accounts);325}326327async getAccounts(providerId: string): Promise<readonly AuthenticationSessionAccount[]> {328return this.accountsMap.get(providerId) || [];329}330331// All other methods are stubs since we don't test them332get declaredProviders(): any[] { return []; }333isDynamicAuthenticationProvider(): boolean { return false; }334async getSessions(): Promise<readonly AuthenticationSession[]> { return []; }335async createSession(): Promise<AuthenticationSession> { return createSession(); }336async removeSession(): Promise<void> { }337manageTrustedExtensionsForAccount(): void { }338async removeAccountSessions(): Promise<void> { }339registerDeclaredAuthenticationProvider(): void { }340unregisterDeclaredAuthenticationProvider(): void { }341unregisterAuthenticationProvider(): void { }342registerAuthenticationProviderHostDelegate(): IDisposable { return { dispose: () => { } }; }343createDynamicAuthenticationProvider(): Promise<any> { return Promise.resolve(undefined); }344async requestNewSession(): Promise<AuthenticationSession> { return createSession(); }345async getSession(): Promise<AuthenticationSession | undefined> { return createSession(); }346getOrActivateProviderIdForServer(): Promise<string | undefined> { return Promise.resolve(undefined); }347supportsHeimdallConnection(): boolean { return false; }348}349350351