Path: blob/main/src/vs/workbench/services/authentication/test/browser/authenticationQueryService.test.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 * as assert from 'assert';6import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';7import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';8import { ILogService, NullLogService } from '../../../../../platform/log/common/log.js';9import { IStorageService } from '../../../../../platform/storage/common/storage.js';10import { TestStorageService } from '../../../../test/common/workbenchTestServices.js';11import { IAuthenticationQueryService } from '../../common/authenticationQuery.js';12import { AuthenticationQueryService } from '../../browser/authenticationQueryService.js';13import { IAuthenticationService, IAuthenticationExtensionsService } from '../../common/authentication.js';14import { IAuthenticationUsageService } from '../../browser/authenticationUsageService.js';15import { IAuthenticationMcpUsageService } from '../../browser/authenticationMcpUsageService.js';16import { IAuthenticationAccessService } from '../../browser/authenticationAccessService.js';17import { IAuthenticationMcpAccessService } from '../../browser/authenticationMcpAccessService.js';18import { IAuthenticationMcpService } from '../../browser/authenticationMcpService.js';19import {20TestUsageService,21TestMcpUsageService,22TestAccessService,23TestMcpAccessService,24TestExtensionsService,25TestMcpService,26TestAuthenticationService,27createProvider,28} from './authenticationQueryServiceMocks.js';2930/**31* Real integration tests for AuthenticationQueryService32*/33suite('AuthenticationQueryService Integration Tests', () => {34const disposables = ensureNoDisposablesAreLeakedInTestSuite();3536let queryService: IAuthenticationQueryService;37let authService: TestAuthenticationService;38let usageService: TestUsageService;39let mcpUsageService: TestMcpUsageService;40let accessService: TestAccessService;41let mcpAccessService: TestMcpAccessService;4243setup(() => {44const instantiationService = disposables.add(new TestInstantiationService());4546// Set up storage service47const storageService = disposables.add(new TestStorageService());48instantiationService.stub(IStorageService, storageService);4950// Set up log service51instantiationService.stub(ILogService, new NullLogService());5253// Create and register test services54authService = disposables.add(new TestAuthenticationService());55instantiationService.stub(IAuthenticationService, authService);5657usageService = disposables.add(new TestUsageService());58mcpUsageService = disposables.add(new TestMcpUsageService());59accessService = disposables.add(new TestAccessService());60mcpAccessService = disposables.add(new TestMcpAccessService());6162instantiationService.stub(IAuthenticationUsageService, usageService);63instantiationService.stub(IAuthenticationMcpUsageService, mcpUsageService);64instantiationService.stub(IAuthenticationAccessService, accessService);65instantiationService.stub(IAuthenticationMcpAccessService, mcpAccessService);66instantiationService.stub(IAuthenticationExtensionsService, disposables.add(new TestExtensionsService()));67instantiationService.stub(IAuthenticationMcpService, disposables.add(new TestMcpService()));6869// Create the query service70queryService = disposables.add(instantiationService.createInstance(AuthenticationQueryService));71});7273test('usage tracking stores and retrieves data correctly', () => {74const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');7576// Initially no usage77assert.strictEqual(extensionQuery.getUsage().length, 0);7879// Add usage and verify it's stored80extensionQuery.addUsage(['read', 'write'], 'My Extension');81const usage = extensionQuery.getUsage();82assert.strictEqual(usage.length, 1);83assert.strictEqual(usage[0].extensionId, 'my-extension');84assert.strictEqual(usage[0].extensionName, 'My Extension');85assert.deepStrictEqual(usage[0].scopes, ['read', 'write']);8687// Add more usage and verify accumulation88extensionQuery.addUsage(['admin'], 'My Extension');89assert.strictEqual(extensionQuery.getUsage().length, 2);90});9192test('access control persists across queries', () => {93const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');9495// Set access and verify96extensionQuery.setAccessAllowed(true, 'My Extension');97assert.strictEqual(extensionQuery.isAccessAllowed(), true);9899// Create new query object for same target - should persist100const sameExtensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');101assert.strictEqual(sameExtensionQuery.isAccessAllowed(), true);102103// Different extension should be unaffected104const otherExtensionQuery = queryService.provider('github').account('[email protected]').extension('other-extension');105assert.strictEqual(otherExtensionQuery.isAccessAllowed(), undefined);106});107108test('account preferences work across services', () => {109const extensionQuery = queryService.provider('github').extension('my-extension');110const mcpQuery = queryService.provider('github').mcpServer('my-server');111112// Set preferences for both113extensionQuery.setPreferredAccount({ id: 'user1', label: '[email protected]' });114mcpQuery.setPreferredAccount({ id: 'user2', label: '[email protected]' });115116// Verify different preferences are stored independently117assert.strictEqual(extensionQuery.getPreferredAccount(), '[email protected]');118assert.strictEqual(mcpQuery.getPreferredAccount(), '[email protected]');119120// Test preference detection121const userExtensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');122const adminMcpQuery = queryService.provider('github').account('[email protected]').mcpServer('my-server');123124assert.strictEqual(userExtensionQuery.isPreferred(), true);125assert.strictEqual(adminMcpQuery.isPreferred(), true);126127// Test non-preferred accounts128const wrongExtensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');129assert.strictEqual(wrongExtensionQuery.isPreferred(), false);130});131132test('account removal cleans up all related data', () => {133const accountQuery = queryService.provider('github').account('[email protected]');134135// Set up data across multiple services136accountQuery.extension('ext1').setAccessAllowed(true, 'Extension 1');137accountQuery.extension('ext1').addUsage(['read'], 'Extension 1');138accountQuery.mcpServer('mcp1').setAccessAllowed(true, 'MCP Server 1');139accountQuery.mcpServer('mcp1').addUsage(['write'], 'MCP Server 1');140141// Verify data exists142assert.strictEqual(accountQuery.extension('ext1').isAccessAllowed(), true);143assert.strictEqual(accountQuery.extension('ext1').getUsage().length, 1);144assert.strictEqual(accountQuery.mcpServer('mcp1').isAccessAllowed(), true);145assert.strictEqual(accountQuery.mcpServer('mcp1').getUsage().length, 1);146147// Remove account148accountQuery.remove();149150// Verify all data is cleaned up151assert.strictEqual(accountQuery.extension('ext1').isAccessAllowed(), undefined);152assert.strictEqual(accountQuery.extension('ext1').getUsage().length, 0);153assert.strictEqual(accountQuery.mcpServer('mcp1').isAccessAllowed(), undefined);154assert.strictEqual(accountQuery.mcpServer('mcp1').getUsage().length, 0);155});156157test('provider registration and listing works', () => {158// Initially no providers159assert.strictEqual(queryService.getProviderIds().length, 0);160161// Register a provider162const provider = createProvider({ id: 'github', label: 'GitHub' });163authService.registerAuthenticationProvider('github', provider);164165// Verify provider is listed166const providerIds = queryService.getProviderIds();167assert.ok(providerIds.includes('github'));168assert.strictEqual(authService.isAuthenticationProviderRegistered('github'), true);169});170171test('MCP usage and access work independently from extensions', () => {172const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');173const mcpQuery = queryService.provider('github').account('[email protected]').mcpServer('my-server');174175// Set up data for both176extensionQuery.setAccessAllowed(true, 'My Extension');177extensionQuery.addUsage(['read'], 'My Extension');178179mcpQuery.setAccessAllowed(false, 'My Server');180mcpQuery.addUsage(['write'], 'My Server');181182// Verify they're independent183assert.strictEqual(extensionQuery.isAccessAllowed(), true);184assert.strictEqual(mcpQuery.isAccessAllowed(), false);185186assert.strictEqual(extensionQuery.getUsage()[0].extensionId, 'my-extension');187assert.strictEqual(mcpQuery.getUsage()[0].mcpServerId, 'my-server');188189// Verify no cross-contamination190assert.strictEqual(extensionQuery.getUsage().length, 1);191assert.strictEqual(mcpQuery.getUsage().length, 1);192});193194test('getAllAccountPreferences returns synchronously', () => {195// Register providers for the test196const githubProvider = createProvider({ id: 'github', label: 'GitHub' });197const azureProvider = createProvider({ id: 'azure', label: 'Azure' });198authService.registerAuthenticationProvider('github', githubProvider);199authService.registerAuthenticationProvider('azure', azureProvider);200201const extensionQuery = queryService.extension('my-extension');202const mcpQuery = queryService.mcpServer('my-server');203204// Set preferences for different providers205extensionQuery.provider('github').setPreferredAccount({ id: 'user1', label: '[email protected]' });206extensionQuery.provider('azure').setPreferredAccount({ id: 'user2', label: '[email protected]' });207mcpQuery.provider('github').setPreferredAccount({ id: 'user3', label: '[email protected]' });208209// Get all preferences synchronously (no await needed)210const extensionPreferences = extensionQuery.getAllAccountPreferences();211const mcpPreferences = mcpQuery.getAllAccountPreferences();212213// Verify extension preferences214assert.strictEqual(extensionPreferences.get('github'), '[email protected]');215assert.strictEqual(extensionPreferences.get('azure'), '[email protected]');216assert.strictEqual(extensionPreferences.size, 2);217218// Verify MCP preferences219assert.strictEqual(mcpPreferences.get('github'), '[email protected]');220assert.strictEqual(mcpPreferences.size, 1);221222// Verify they don't interfere with each other223assert.notStrictEqual(extensionPreferences.get('github'), mcpPreferences.get('github'));224});225226test('forEach methods work synchronously', () => {227const accountQuery = queryService.provider('github').account('[email protected]');228229// Add some usage data first230accountQuery.extension('ext1').addUsage(['read'], 'Extension 1');231accountQuery.extension('ext2').addUsage(['write'], 'Extension 2');232accountQuery.mcpServer('mcp1').addUsage(['admin'], 'MCP Server 1');233234// Test extensions forEach - no await needed235const extensionIds: string[] = [];236accountQuery.extensions().forEach(extensionQuery => {237extensionIds.push(extensionQuery.extensionId);238});239240assert.strictEqual(extensionIds.length, 2);241assert.ok(extensionIds.includes('ext1'));242assert.ok(extensionIds.includes('ext2'));243244// Test MCP servers forEach - no await needed245const mcpServerIds: string[] = [];246accountQuery.mcpServers().forEach(mcpServerQuery => {247mcpServerIds.push(mcpServerQuery.mcpServerId);248});249250assert.strictEqual(mcpServerIds.length, 1);251assert.ok(mcpServerIds.includes('mcp1'));252});253254test('remove method works synchronously', () => {255const accountQuery = queryService.provider('github').account('[email protected]');256257// Set up data258accountQuery.extension('ext1').setAccessAllowed(true, 'Extension 1');259accountQuery.mcpServer('mcp1').setAccessAllowed(true, 'MCP Server 1');260261// Remove synchronously - no await needed262accountQuery.remove();263264// Verify data is gone265assert.strictEqual(accountQuery.extension('ext1').isAccessAllowed(), undefined);266assert.strictEqual(accountQuery.mcpServer('mcp1').isAccessAllowed(), undefined);267});268269test('cross-provider extension queries work correctly', () => {270// Register multiple providers271const githubProvider = createProvider({ id: 'github', label: 'GitHub' });272const azureProvider = createProvider({ id: 'azure', label: 'Azure' });273authService.registerAuthenticationProvider('github', githubProvider);274authService.registerAuthenticationProvider('azure', azureProvider);275276// Set up data using provider-first approach277queryService.provider('github').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');278queryService.provider('azure').account('[email protected]').extension('my-extension').setAccessAllowed(false, 'My Extension');279280// Query using extension-first approach should return all providers281const extensionQuery = queryService.extension('my-extension');282const githubPrefs = extensionQuery.getAllAccountPreferences();283284// Should include both providers285assert.ok(githubPrefs.size >= 0); // Extension query should work across providers286287// Test preferences using extension-first query pattern288extensionQuery.provider('github').setPreferredAccount({ id: 'user1', label: '[email protected]' });289extensionQuery.provider('azure').setPreferredAccount({ id: 'user2', label: '[email protected]' });290291assert.strictEqual(extensionQuery.provider('github').getPreferredAccount(), '[email protected]');292assert.strictEqual(extensionQuery.provider('azure').getPreferredAccount(), '[email protected]');293});294295test('event forwarding from authentication service works', () => {296let eventFired = false;297298// Listen for access change events through the query service299const disposable = queryService.onDidChangeAccess(() => {300eventFired = true;301});302303try {304// Trigger an access change that should fire an event305queryService.provider('github').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');306307// Verify the event was fired308assert.strictEqual(eventFired, true);309} finally {310disposable.dispose();311}312});313314test('error handling for invalid inputs works correctly', () => {315// Test with non-existent provider316const invalidProviderQuery = queryService.provider('non-existent-provider');317318// Should not throw, but should handle gracefully319assert.doesNotThrow(() => {320invalidProviderQuery.account('[email protected]').extension('my-extension').isAccessAllowed();321});322323// Test with empty/invalid account names324const emptyAccountQuery = queryService.provider('github').account('').extension('my-extension');325assert.doesNotThrow(() => {326emptyAccountQuery.isAccessAllowed();327});328329// Test with empty extension IDs330const emptyExtensionQuery = queryService.provider('github').account('[email protected]').extension('');331assert.doesNotThrow(() => {332emptyExtensionQuery.isAccessAllowed();333});334});335336test('bulk operations work correctly', () => {337const accountQuery = queryService.provider('github').account('[email protected]');338339// Set up multiple extensions with different access levels340accountQuery.extension('ext1').setAccessAllowed(true, 'Extension 1');341accountQuery.extension('ext2').setAccessAllowed(false, 'Extension 2');342accountQuery.extension('ext3').setAccessAllowed(true, 'Extension 3');343344// Add usage for some extensions345accountQuery.extension('ext1').addUsage(['read'], 'Extension 1');346accountQuery.extension('ext3').addUsage(['write'], 'Extension 3');347348// Test bulk enumeration349let extensionCount = 0;350let allowedCount = 0;351let usageCount = 0;352353accountQuery.extensions().forEach(extensionQuery => {354extensionCount++;355if (extensionQuery.isAccessAllowed() === true) {356allowedCount++;357}358if (extensionQuery.getUsage().length > 0) {359usageCount++;360}361});362363// Verify bulk operation results364assert.strictEqual(extensionCount, 3);365assert.strictEqual(allowedCount, 2); // ext1 and ext3366assert.strictEqual(usageCount, 2); // ext1 and ext3367368// Test bulk operations for MCP servers369accountQuery.mcpServer('mcp1').setAccessAllowed(true, 'MCP 1');370accountQuery.mcpServer('mcp2').setAccessAllowed(false, 'MCP 2');371372let mcpCount = 0;373accountQuery.mcpServers().forEach(mcpQuery => {374mcpCount++;375});376377assert.strictEqual(mcpCount, 2);378});379380test('data consistency across different query paths', () => {381// Set up data using one query path382const extensionQuery1 = queryService.provider('github').account('[email protected]').extension('my-extension');383extensionQuery1.setAccessAllowed(true, 'My Extension');384extensionQuery1.addUsage(['read', 'write'], 'My Extension');385386// Access same data using different query path (cross-provider query)387const extensionQuery2 = queryService.extension('my-extension').provider('github');388389// Data should be consistent through provider preference access390assert.strictEqual(extensionQuery1.isAccessAllowed(), true);391assert.strictEqual(extensionQuery1.getUsage().length, 1);392393// Set preferences and check consistency394extensionQuery2.setPreferredAccount({ id: 'user', label: '[email protected]' });395assert.strictEqual(extensionQuery2.getPreferredAccount(), '[email protected]');396397// Modify through one path398extensionQuery1.setAccessAllowed(false, 'My Extension');399400// Should be reflected when accessing through provider->account path401assert.strictEqual(extensionQuery1.isAccessAllowed(), false);402});403404test('preference management handles complex scenarios', () => {405// Register multiple providers406const githubProvider = createProvider({ id: 'github', label: 'GitHub' });407const azureProvider = createProvider({ id: 'azure', label: 'Azure' });408authService.registerAuthenticationProvider('github', githubProvider);409authService.registerAuthenticationProvider('azure', azureProvider);410411const extensionQuery = queryService.extension('my-extension');412413// Set different preferences for different providers414extensionQuery.provider('github').setPreferredAccount({ id: 'user1', label: '[email protected]' });415extensionQuery.provider('azure').setPreferredAccount({ id: 'user2', label: '[email protected]' });416417// Test preference retrieval418assert.strictEqual(extensionQuery.provider('github').getPreferredAccount(), '[email protected]');419assert.strictEqual(extensionQuery.provider('azure').getPreferredAccount(), '[email protected]');420421// Test account preference detection through provider->account queries422assert.strictEqual(423queryService.provider('github').account('[email protected]').extension('my-extension').isPreferred(),424true425);426assert.strictEqual(427queryService.provider('azure').account('[email protected]').extension('my-extension').isPreferred(),428true429);430assert.strictEqual(431queryService.provider('github').account('[email protected]').extension('my-extension').isPreferred(),432false433);434435// Test getAllAccountPreferences with multiple providers436const allPrefs = extensionQuery.getAllAccountPreferences();437assert.strictEqual(allPrefs.get('github'), '[email protected]');438assert.strictEqual(allPrefs.get('azure'), '[email protected]');439assert.strictEqual(allPrefs.size, 2);440});441442test('MCP server vs extension data isolation is complete', () => {443const accountQuery = queryService.provider('github').account('[email protected]');444445// Set up similar data for extension and MCP server with same IDs446const sameId = 'same-identifier';447accountQuery.extension(sameId).setAccessAllowed(true, 'Extension');448accountQuery.extension(sameId).addUsage(['ext-scope'], 'Extension');449450accountQuery.mcpServer(sameId).setAccessAllowed(false, 'MCP Server');451accountQuery.mcpServer(sameId).addUsage(['mcp-scope'], 'MCP Server');452453// Verify complete isolation454assert.strictEqual(accountQuery.extension(sameId).isAccessAllowed(), true);455assert.strictEqual(accountQuery.mcpServer(sameId).isAccessAllowed(), false);456457const extUsage = accountQuery.extension(sameId).getUsage();458const mcpUsage = accountQuery.mcpServer(sameId).getUsage();459460assert.strictEqual(extUsage.length, 1);461assert.strictEqual(mcpUsage.length, 1);462assert.strictEqual(extUsage[0].extensionId, sameId);463assert.strictEqual(mcpUsage[0].mcpServerId, sameId);464assert.notDeepStrictEqual(extUsage[0].scopes, mcpUsage[0].scopes);465466// Test preference isolation467queryService.extension(sameId).provider('github').setPreferredAccount({ id: 'ext-user', label: '[email protected]' });468queryService.mcpServer(sameId).provider('github').setPreferredAccount({ id: 'mcp-user', label: '[email protected]' });469470assert.strictEqual(queryService.extension(sameId).provider('github').getPreferredAccount(), '[email protected]');471assert.strictEqual(queryService.mcpServer(sameId).provider('github').getPreferredAccount(), '[email protected]');472});473474test('provider listing and registration integration', () => {475// Initially should have providers from setup (if any)476const initialProviders = queryService.getProviderIds();477const initialCount = initialProviders.length;478479// Register a new provider480const newProvider = createProvider({ id: 'test-provider', label: 'Test Provider' });481authService.registerAuthenticationProvider('test-provider', newProvider);482483// Should now appear in listing484const updatedProviders = queryService.getProviderIds();485assert.strictEqual(updatedProviders.length, initialCount + 1);486assert.ok(updatedProviders.includes('test-provider'));487488// Should be able to query the new provider489const providerQuery = queryService.provider('test-provider');490assert.strictEqual(providerQuery.providerId, 'test-provider');491492// Should integrate with authentication service state493assert.strictEqual(authService.isAuthenticationProviderRegistered('test-provider'), true);494});495496/**497* Service Call Verification Tests498* These tests verify that the AuthenticationQueryService properly delegates to underlying services499* with the correct parameters. This is important for ensuring the facade works correctly.500*/501test('setAccessAllowed calls updateAllowedExtensions with correct parameters', () => {502const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');503504// Clear any previous calls505accessService.clearCallHistory();506507// Call setAccessAllowed508extensionQuery.setAccessAllowed(true, 'My Extension');509510// Verify the underlying service was called correctly511const calls = accessService.getCallsFor('updateAllowedExtensions');512assert.strictEqual(calls.length, 1);513514const [providerId, accountName, extensions] = calls[0].args;515assert.strictEqual(providerId, 'github');516assert.strictEqual(accountName, '[email protected]');517assert.strictEqual(extensions.length, 1);518assert.strictEqual(extensions[0].id, 'my-extension');519assert.strictEqual(extensions[0].name, 'My Extension');520assert.strictEqual(extensions[0].allowed, true);521});522523test('addUsage calls addAccountUsage with correct parameters', () => {524const extensionQuery = queryService.provider('azure').account('[email protected]').extension('test-extension');525526// Clear any previous calls527usageService.clearCallHistory();528529// Call addUsage530extensionQuery.addUsage(['read', 'write'], 'Test Extension');531532// Verify the underlying service was called correctly533const calls = usageService.getCallsFor('addAccountUsage');534assert.strictEqual(calls.length, 1);535536const [providerId, accountName, scopes, extensionId, extensionName] = calls[0].args;537assert.strictEqual(providerId, 'azure');538assert.strictEqual(accountName, '[email protected]');539assert.deepStrictEqual(scopes, ['read', 'write']);540assert.strictEqual(extensionId, 'test-extension');541assert.strictEqual(extensionName, 'Test Extension');542});543544test('isAccessAllowed calls underlying service with correct parameters', () => {545const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');546547// Clear any previous calls548accessService.clearCallHistory();549550// Call isAccessAllowed551extensionQuery.isAccessAllowed();552553// Verify the underlying service was called correctly554const calls = accessService.getCallsFor('isAccessAllowed');555assert.strictEqual(calls.length, 1);556557const [providerId, accountName, extensionId] = calls[0].args;558assert.strictEqual(providerId, 'github');559assert.strictEqual(accountName, '[email protected]');560assert.strictEqual(extensionId, 'my-extension');561});562563test('getUsage calls readAccountUsages with correct parameters', () => {564const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');565566// Clear any previous calls567usageService.clearCallHistory();568569// Call getUsage570extensionQuery.getUsage();571572// Verify the underlying service was called correctly573const calls = usageService.getCallsFor('readAccountUsages');574assert.strictEqual(calls.length, 1);575576const [providerId, accountName] = calls[0].args;577assert.strictEqual(providerId, 'github');578assert.strictEqual(accountName, '[email protected]');579});580581test('MCP setAccessAllowed calls updateAllowedMcpServers with correct parameters', () => {582const mcpQuery = queryService.provider('github').account('[email protected]').mcpServer('my-server');583584// Clear any previous calls585mcpAccessService.clearCallHistory();586587// Call setAccessAllowed588mcpQuery.setAccessAllowed(false, 'My MCP Server');589590// Verify the underlying service was called correctly591const calls = mcpAccessService.getCallsFor('updateAllowedMcpServers');592assert.strictEqual(calls.length, 1);593594const [providerId, accountName, servers] = calls[0].args;595assert.strictEqual(providerId, 'github');596assert.strictEqual(accountName, '[email protected]');597assert.strictEqual(servers.length, 1);598assert.strictEqual(servers[0].id, 'my-server');599assert.strictEqual(servers[0].name, 'My MCP Server');600assert.strictEqual(servers[0].allowed, false);601});602603test('MCP addUsage calls addAccountUsage with correct parameters', () => {604const mcpQuery = queryService.provider('azure').account('[email protected]').mcpServer('test-server');605606// Clear any previous calls607mcpUsageService.clearCallHistory();608609// Call addUsage610mcpQuery.addUsage(['admin'], 'Test MCP Server');611612// Verify the underlying service was called correctly613const calls = mcpUsageService.getCallsFor('addAccountUsage');614assert.strictEqual(calls.length, 1);615616const [providerId, accountName, scopes, serverId, serverName] = calls[0].args;617assert.strictEqual(providerId, 'azure');618assert.strictEqual(accountName, '[email protected]');619assert.deepStrictEqual(scopes, ['admin']);620assert.strictEqual(serverId, 'test-server');621assert.strictEqual(serverName, 'Test MCP Server');622});623624test('account removal calls all appropriate cleanup methods', () => {625const accountQuery = queryService.provider('github').account('[email protected]');626627// Set up some data first628accountQuery.extension('ext1').setAccessAllowed(true, 'Extension 1');629accountQuery.extension('ext1').addUsage(['read'], 'Extension 1');630accountQuery.mcpServer('mcp1').setAccessAllowed(true, 'MCP Server 1');631accountQuery.mcpServer('mcp1').addUsage(['write'], 'MCP Server 1');632633// Clear call history to focus on removal calls634usageService.clearCallHistory();635mcpUsageService.clearCallHistory();636accessService.clearCallHistory();637mcpAccessService.clearCallHistory();638639// Call remove640accountQuery.remove();641642// Verify all cleanup methods were called643const extensionUsageRemoval = usageService.getCallsFor('removeAccountUsage');644const mcpUsageRemoval = mcpUsageService.getCallsFor('removeAccountUsage');645const extensionAccessRemoval = accessService.getCallsFor('removeAllowedExtensions');646const mcpAccessRemoval = mcpAccessService.getCallsFor('removeAllowedMcpServers');647648assert.strictEqual(extensionUsageRemoval.length, 1);649assert.strictEqual(mcpUsageRemoval.length, 1);650assert.strictEqual(extensionAccessRemoval.length, 1);651assert.strictEqual(mcpAccessRemoval.length, 1);652653// Verify all calls use correct parameters654[extensionUsageRemoval[0], mcpUsageRemoval[0], extensionAccessRemoval[0], mcpAccessRemoval[0]].forEach(call => {655const [providerId, accountName] = call.args;656assert.strictEqual(providerId, 'github');657assert.strictEqual(accountName, '[email protected]');658});659});660661test('bulk operations call readAccountUsages and readAllowedExtensions', () => {662const accountQuery = queryService.provider('github').account('[email protected]');663664// Set up some data665accountQuery.extension('ext1').setAccessAllowed(true, 'Extension 1');666accountQuery.extension('ext2').addUsage(['read'], 'Extension 2');667668// Clear call history669usageService.clearCallHistory();670accessService.clearCallHistory();671672// Perform bulk operation673accountQuery.extensions().forEach(() => {674// Just iterate to trigger the underlying service calls675});676677// Verify the underlying services were called for bulk enumeration678const usageCalls = usageService.getCallsFor('readAccountUsages');679const accessCalls = accessService.getCallsFor('readAllowedExtensions');680681assert.strictEqual(usageCalls.length, 1);682assert.strictEqual(accessCalls.length, 1);683684// Verify parameters685usageCalls.concat(accessCalls).forEach(call => {686const [providerId, accountName] = call.args;687assert.strictEqual(providerId, 'github');688assert.strictEqual(accountName, '[email protected]');689});690});691692test('multiple operations accumulate service calls correctly', () => {693const extensionQuery = queryService.provider('github').account('[email protected]').extension('my-extension');694695// Clear call history696accessService.clearCallHistory();697usageService.clearCallHistory();698699// Perform multiple operations700extensionQuery.setAccessAllowed(true, 'My Extension');701extensionQuery.addUsage(['read'], 'My Extension');702extensionQuery.isAccessAllowed();703extensionQuery.getUsage();704extensionQuery.setAccessAllowed(false, 'My Extension');705706// Verify call counts707assert.strictEqual(accessService.getCallsFor('updateAllowedExtensions').length, 2);708assert.strictEqual(accessService.getCallsFor('isAccessAllowed').length, 1);709assert.strictEqual(usageService.getCallsFor('addAccountUsage').length, 1);710assert.strictEqual(usageService.getCallsFor('readAccountUsages').length, 1);711});712713test('getProvidersWithAccess filters internal providers by default', async () => {714// Register multiple providers including internal ones715const githubProvider = createProvider({ id: 'github', label: 'GitHub' });716const azureProvider = createProvider({ id: 'azure', label: 'Azure' });717const internalProvider1 = createProvider({ id: '__internal1', label: 'Internal Provider 1' });718const internalProvider2 = createProvider({ id: '__internal2', label: 'Internal Provider 2' });719720authService.registerAuthenticationProvider('github', githubProvider);721authService.registerAuthenticationProvider('azure', azureProvider);722authService.registerAuthenticationProvider('__internal1', internalProvider1);723authService.registerAuthenticationProvider('__internal2', internalProvider2);724725// Add accounts to all providers726authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);727authService.addAccounts('azure', [{ id: 'user2', label: '[email protected]' }]);728authService.addAccounts('__internal1', [{ id: 'user3', label: '[email protected]' }]);729authService.addAccounts('__internal2', [{ id: 'user4', label: '[email protected]' }]);730731// Set up access for all providers732queryService.provider('github').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');733queryService.provider('azure').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');734queryService.provider('__internal1').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');735queryService.provider('__internal2').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');736737// Test extension query - should exclude internal providers by default738const extensionQuery = queryService.extension('my-extension');739const providersWithAccess = await extensionQuery.getProvidersWithAccess();740741assert.strictEqual(providersWithAccess.length, 2);742assert.ok(providersWithAccess.includes('github'));743assert.ok(providersWithAccess.includes('azure'));744assert.ok(!providersWithAccess.includes('__internal1'));745assert.ok(!providersWithAccess.includes('__internal2'));746});747748test('getProvidersWithAccess includes internal providers when requested', async () => {749// Register multiple providers including internal ones750const githubProvider = createProvider({ id: 'github', label: 'GitHub' });751const internalProvider = createProvider({ id: '__internal1', label: 'Internal Provider' });752753authService.registerAuthenticationProvider('github', githubProvider);754authService.registerAuthenticationProvider('__internal1', internalProvider);755756// Add accounts to all providers757authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);758authService.addAccounts('__internal1', [{ id: 'user2', label: '[email protected]' }]);759760// Set up access for all providers761queryService.provider('github').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');762queryService.provider('__internal1').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');763764// Test extension query - should include internal providers when requested765const extensionQuery = queryService.extension('my-extension');766const providersWithAccess = await extensionQuery.getProvidersWithAccess(true);767768assert.strictEqual(providersWithAccess.length, 2);769assert.ok(providersWithAccess.includes('github'));770assert.ok(providersWithAccess.includes('__internal1'));771});772773test('MCP server getProvidersWithAccess filters internal providers by default', async () => {774// Register multiple providers including internal ones775const githubProvider = createProvider({ id: 'github', label: 'GitHub' });776const azureProvider = createProvider({ id: 'azure', label: 'Azure' });777const internalProvider1 = createProvider({ id: '__internal1', label: 'Internal Provider 1' });778const internalProvider2 = createProvider({ id: '__internal2', label: 'Internal Provider 2' });779780authService.registerAuthenticationProvider('github', githubProvider);781authService.registerAuthenticationProvider('azure', azureProvider);782authService.registerAuthenticationProvider('__internal1', internalProvider1);783authService.registerAuthenticationProvider('__internal2', internalProvider2);784785// Add accounts to all providers786authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);787authService.addAccounts('azure', [{ id: 'user2', label: '[email protected]' }]);788authService.addAccounts('__internal1', [{ id: 'user3', label: '[email protected]' }]);789authService.addAccounts('__internal2', [{ id: 'user4', label: '[email protected]' }]);790791// Set up MCP access for all providers792queryService.provider('github').account('[email protected]').mcpServer('my-server').setAccessAllowed(true, 'My Server');793queryService.provider('azure').account('[email protected]').mcpServer('my-server').setAccessAllowed(true, 'My Server');794queryService.provider('__internal1').account('[email protected]').mcpServer('my-server').setAccessAllowed(true, 'My Server');795queryService.provider('__internal2').account('[email protected]').mcpServer('my-server').setAccessAllowed(true, 'My Server');796797// Test MCP server query - should exclude internal providers by default798const mcpServerQuery = queryService.mcpServer('my-server');799const providersWithAccess = await mcpServerQuery.getProvidersWithAccess();800801assert.strictEqual(providersWithAccess.length, 2);802assert.ok(providersWithAccess.includes('github'));803assert.ok(providersWithAccess.includes('azure'));804assert.ok(!providersWithAccess.includes('__internal1'));805assert.ok(!providersWithAccess.includes('__internal2'));806});807808test('MCP server getProvidersWithAccess includes internal providers when requested', async () => {809// Register multiple providers including internal ones810const githubProvider = createProvider({ id: 'github', label: 'GitHub' });811const internalProvider = createProvider({ id: '__internal1', label: 'Internal Provider' });812813authService.registerAuthenticationProvider('github', githubProvider);814authService.registerAuthenticationProvider('__internal1', internalProvider);815816// Add accounts to all providers817authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);818authService.addAccounts('__internal1', [{ id: 'user2', label: '[email protected]' }]);819820// Set up MCP access for all providers821queryService.provider('github').account('[email protected]').mcpServer('my-server').setAccessAllowed(true, 'My Server');822queryService.provider('__internal1').account('[email protected]').mcpServer('my-server').setAccessAllowed(true, 'My Server');823824// Test MCP server query - should include internal providers when requested825const mcpServerQuery = queryService.mcpServer('my-server');826const providersWithAccess = await mcpServerQuery.getProvidersWithAccess(true);827828assert.strictEqual(providersWithAccess.length, 2);829assert.ok(providersWithAccess.includes('github'));830assert.ok(providersWithAccess.includes('__internal1'));831});832833test('internal provider filtering works with mixed access patterns', async () => {834// Register mixed providers835const normalProvider = createProvider({ id: 'normal', label: 'Normal Provider' });836const internalProvider = createProvider({ id: '__internal', label: 'Internal Provider' });837const noAccessProvider = createProvider({ id: 'no-access', label: 'No Access Provider' });838839authService.registerAuthenticationProvider('normal', normalProvider);840authService.registerAuthenticationProvider('__internal', internalProvider);841authService.registerAuthenticationProvider('no-access', noAccessProvider);842843// Add accounts to all providers844authService.addAccounts('normal', [{ id: 'user1', label: '[email protected]' }]);845authService.addAccounts('__internal', [{ id: 'user2', label: '[email protected]' }]);846authService.addAccounts('no-access', [{ id: 'user3', label: '[email protected]' }]);847848// Set up access only for normal and internal providers849queryService.provider('normal').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');850queryService.provider('__internal').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');851// Note: no-access provider deliberately has no access set852853const extensionQuery = queryService.extension('my-extension');854855// Without includeInternal: should only return normal provider856const providersWithoutInternal = await extensionQuery.getProvidersWithAccess(false);857assert.strictEqual(providersWithoutInternal.length, 1);858assert.ok(providersWithoutInternal.includes('normal'));859assert.ok(!providersWithoutInternal.includes('__internal'));860assert.ok(!providersWithoutInternal.includes('no-access'));861862// With includeInternal: should return both normal and internal863const providersWithInternal = await extensionQuery.getProvidersWithAccess(true);864assert.strictEqual(providersWithInternal.length, 2);865assert.ok(providersWithInternal.includes('normal'));866assert.ok(providersWithInternal.includes('__internal'));867assert.ok(!providersWithInternal.includes('no-access'));868});869870test('internal provider filtering respects the __ prefix exactly', async () => {871// Register providers with various naming patterns872const regularProvider = createProvider({ id: 'regular', label: 'Regular Provider' });873const underscoreProvider = createProvider({ id: '_single', label: 'Single Underscore Provider' });874const doubleUnderscoreProvider = createProvider({ id: '__double', label: 'Double Underscore Provider' });875const tripleUnderscoreProvider = createProvider({ id: '___triple', label: 'Triple Underscore Provider' });876const underscoreInMiddleProvider = createProvider({ id: 'mid_underscore', label: 'Middle Underscore Provider' });877878authService.registerAuthenticationProvider('regular', regularProvider);879authService.registerAuthenticationProvider('_single', underscoreProvider);880authService.registerAuthenticationProvider('__double', doubleUnderscoreProvider);881authService.registerAuthenticationProvider('___triple', tripleUnderscoreProvider);882authService.registerAuthenticationProvider('mid_underscore', underscoreInMiddleProvider);883884// Add accounts to all providers885authService.addAccounts('regular', [{ id: 'user1', label: '[email protected]' }]);886authService.addAccounts('_single', [{ id: 'user2', label: '[email protected]' }]);887authService.addAccounts('__double', [{ id: 'user3', label: '[email protected]' }]);888authService.addAccounts('___triple', [{ id: 'user4', label: '[email protected]' }]);889authService.addAccounts('mid_underscore', [{ id: 'user5', label: '[email protected]' }]);890891// Set up access for all providers892queryService.provider('regular').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');893queryService.provider('_single').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');894queryService.provider('__double').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');895queryService.provider('___triple').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');896queryService.provider('mid_underscore').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');897898const extensionQuery = queryService.extension('my-extension');899900// Without includeInternal: should exclude only providers starting with exactly "__"901const providersWithoutInternal = await extensionQuery.getProvidersWithAccess(false);902assert.strictEqual(providersWithoutInternal.length, 3);903assert.ok(providersWithoutInternal.includes('regular'));904assert.ok(providersWithoutInternal.includes('_single'));905assert.ok(!providersWithoutInternal.includes('__double'));906assert.ok(!providersWithoutInternal.includes('___triple')); // This starts with __, so should be filtered907assert.ok(providersWithoutInternal.includes('mid_underscore'));908909// With includeInternal: should include all providers910const providersWithInternal = await extensionQuery.getProvidersWithAccess(true);911assert.strictEqual(providersWithInternal.length, 5);912assert.ok(providersWithInternal.includes('regular'));913assert.ok(providersWithInternal.includes('_single'));914assert.ok(providersWithInternal.includes('__double'));915assert.ok(providersWithInternal.includes('___triple'));916assert.ok(providersWithInternal.includes('mid_underscore'));917});918919test('getAllAccountPreferences filters internal providers by default for extensions', () => {920// Register providers921authService.registerAuthenticationProvider('github', createProvider({ id: 'github', label: 'GitHub' }));922authService.registerAuthenticationProvider('azure', createProvider({ id: 'azure', label: 'Azure' }));923authService.registerAuthenticationProvider('__internal', createProvider({ id: '__internal', label: 'Internal' }));924925// Set preferences926const extensionQuery = queryService.extension('my-extension');927extensionQuery.provider('github').setPreferredAccount({ id: 'user1', label: '[email protected]' });928extensionQuery.provider('azure').setPreferredAccount({ id: 'user2', label: '[email protected]' });929extensionQuery.provider('__internal').setPreferredAccount({ id: 'user3', label: '[email protected]' });930931// Without includeInternal: should exclude internal providers932const prefsWithoutInternal = extensionQuery.getAllAccountPreferences(false);933assert.strictEqual(prefsWithoutInternal.size, 2);934assert.strictEqual(prefsWithoutInternal.get('github'), '[email protected]');935assert.strictEqual(prefsWithoutInternal.get('azure'), '[email protected]');936assert.strictEqual(prefsWithoutInternal.get('__internal'), undefined);937938// With includeInternal: should include all providers939const prefsWithInternal = extensionQuery.getAllAccountPreferences(true);940assert.strictEqual(prefsWithInternal.size, 3);941assert.strictEqual(prefsWithInternal.get('github'), '[email protected]');942assert.strictEqual(prefsWithInternal.get('azure'), '[email protected]');943assert.strictEqual(prefsWithInternal.get('__internal'), '[email protected]');944945// Default behavior: should exclude internal providers946const prefsDefault = extensionQuery.getAllAccountPreferences();947assert.strictEqual(prefsDefault.size, 2);948assert.strictEqual(prefsDefault.get('__internal'), undefined);949});950951test('getAllAccountPreferences filters internal providers by default for MCP servers', () => {952// Register providers953authService.registerAuthenticationProvider('github', createProvider({ id: 'github', label: 'GitHub' }));954authService.registerAuthenticationProvider('azure', createProvider({ id: 'azure', label: 'Azure' }));955authService.registerAuthenticationProvider('__internal', createProvider({ id: '__internal', label: 'Internal' }));956957// Set preferences958const mcpQuery = queryService.mcpServer('my-server');959mcpQuery.provider('github').setPreferredAccount({ id: 'user1', label: '[email protected]' });960mcpQuery.provider('azure').setPreferredAccount({ id: 'user2', label: '[email protected]' });961mcpQuery.provider('__internal').setPreferredAccount({ id: 'user3', label: '[email protected]' });962963// Without includeInternal: should exclude internal providers964const prefsWithoutInternal = mcpQuery.getAllAccountPreferences(false);965assert.strictEqual(prefsWithoutInternal.size, 2);966assert.strictEqual(prefsWithoutInternal.get('github'), '[email protected]');967assert.strictEqual(prefsWithoutInternal.get('azure'), '[email protected]');968assert.strictEqual(prefsWithoutInternal.get('__internal'), undefined);969970// With includeInternal: should include all providers971const prefsWithInternal = mcpQuery.getAllAccountPreferences(true);972assert.strictEqual(prefsWithInternal.size, 3);973assert.strictEqual(prefsWithInternal.get('github'), '[email protected]');974assert.strictEqual(prefsWithInternal.get('azure'), '[email protected]');975assert.strictEqual(prefsWithInternal.get('__internal'), '[email protected]');976977// Default behavior: should exclude internal providers978const prefsDefault = mcpQuery.getAllAccountPreferences();979assert.strictEqual(prefsDefault.size, 2);980assert.strictEqual(prefsDefault.get('__internal'), undefined);981});982983test('clearAllData includes internal providers by default', async () => {984// Register providers985authService.registerAuthenticationProvider('github', createProvider({ id: 'github', label: 'GitHub' }));986authService.registerAuthenticationProvider('__internal', createProvider({ id: '__internal', label: 'Internal' }));987988// Add accounts989authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);990authService.addAccounts('__internal', [{ id: 'user2', label: '[email protected]' }]);991992// Set up some data993queryService.provider('github').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');994queryService.provider('__internal').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');995996// Verify data exists997assert.strictEqual(queryService.provider('github').account('[email protected]').extension('my-extension').isAccessAllowed(), true);998assert.strictEqual(queryService.provider('__internal').account('[email protected]').extension('my-extension').isAccessAllowed(), true);9991000// Clear all data (should include internal providers by default)1001await queryService.clearAllData('CLEAR_ALL_AUTH_DATA');10021003// Verify all data is cleared1004assert.strictEqual(queryService.provider('github').account('[email protected]').extension('my-extension').isAccessAllowed(), undefined);1005assert.strictEqual(queryService.provider('__internal').account('[email protected]').extension('my-extension').isAccessAllowed(), undefined);1006});10071008test('clearAllData can exclude internal providers when specified', async () => {1009// Register providers1010authService.registerAuthenticationProvider('github', createProvider({ id: 'github', label: 'GitHub' }));1011authService.registerAuthenticationProvider('__internal', createProvider({ id: '__internal', label: 'Internal' }));10121013// Add accounts1014authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);1015authService.addAccounts('__internal', [{ id: 'user2', label: '[email protected]' }]);10161017// Set up some data1018queryService.provider('github').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');1019queryService.provider('__internal').account('[email protected]').extension('my-extension').setAccessAllowed(true, 'My Extension');10201021// Clear data excluding internal providers1022await queryService.clearAllData('CLEAR_ALL_AUTH_DATA', false);10231024// Verify only non-internal data is cleared1025assert.strictEqual(queryService.provider('github').account('[email protected]').extension('my-extension').isAccessAllowed(), undefined);1026assert.strictEqual(queryService.provider('__internal').account('[email protected]').extension('my-extension').isAccessAllowed(), true);1027});10281029test('isTrusted method works with mock service', () => {1030// Register provider and add account1031authService.registerAuthenticationProvider('github', createProvider({ id: 'github', label: 'GitHub' }));1032authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);10331034// Add a server with trusted state manually to the mock1035mcpAccessService.updateAllowedMcpServers('github', '[email protected]', [{1036id: 'trusted-server',1037name: 'Trusted Server',1038allowed: true,1039trusted: true1040}]);10411042// Add a non-trusted server1043mcpAccessService.updateAllowedMcpServers('github', '[email protected]', [{1044id: 'non-trusted-server',1045name: 'Non-Trusted Server',1046allowed: true1047}]);10481049// Test trusted server1050const trustedQuery = queryService.provider('github').account('[email protected]').mcpServer('trusted-server');1051assert.strictEqual(trustedQuery.isTrusted(), true);10521053// Test non-trusted server1054const nonTrustedQuery = queryService.provider('github').account('[email protected]').mcpServer('non-trusted-server');1055assert.strictEqual(nonTrustedQuery.isTrusted(), false);1056});10571058test('getAllowedMcpServers method returns servers with trusted state', () => {1059// Register provider and add account1060authService.registerAuthenticationProvider('github', createProvider({ id: 'github', label: 'GitHub' }));1061authService.addAccounts('github', [{ id: 'user1', label: '[email protected]' }]);10621063// Add servers manually to the mock1064mcpAccessService.updateAllowedMcpServers('github', '[email protected]', [1065{1066id: 'trusted-server',1067name: 'Trusted Server',1068allowed: true,1069trusted: true1070},1071{1072id: 'user-server',1073name: 'User Server',1074allowed: true1075}1076]);10771078// Get all allowed servers1079const allowedServers = queryService.provider('github').account('[email protected]').mcpServers().getAllowedMcpServers();10801081// Should have both servers1082assert.strictEqual(allowedServers.length, 2);10831084// Find the trusted server1085const trustedServer = allowedServers.find(s => s.id === 'trusted-server');1086assert.ok(trustedServer);1087assert.strictEqual(trustedServer.trusted, true);1088assert.strictEqual(trustedServer.allowed, true);10891090// Find the user-allowed server1091const userServer = allowedServers.find(s => s.id === 'user-server');1092assert.ok(userServer);1093assert.strictEqual(userServer.trusted, undefined);1094assert.strictEqual(userServer.allowed, true);1095});10961097test('getAllowedExtensions returns extension data with trusted state', () => {1098// Set up some extension access data1099const accountQuery = queryService.provider('github').account('[email protected]');1100accountQuery.extension('ext1').setAccessAllowed(true, 'Extension One');1101accountQuery.extension('ext2').setAccessAllowed(true, 'Extension Two');1102accountQuery.extension('ext1').addUsage(['read'], 'Extension One');11031104const allowedExtensions = accountQuery.extensions().getAllowedExtensions();11051106// Should have both extensions1107assert.strictEqual(allowedExtensions.length, 2);11081109// Find the first extension1110const ext1 = allowedExtensions.find(e => e.id === 'ext1');1111assert.ok(ext1);1112assert.strictEqual(ext1.name, 'Extension One');1113assert.strictEqual(ext1.allowed, true);1114assert.strictEqual(ext1.trusted, false); // Not in trusted list1115assert.ok(typeof ext1.lastUsed === 'number');11161117// Find the second extension1118const ext2 = allowedExtensions.find(e => e.id === 'ext2');1119assert.ok(ext2);1120assert.strictEqual(ext2.name, 'Extension Two');1121assert.strictEqual(ext2.allowed, true);1122assert.strictEqual(ext2.trusted, false); // Not in trusted list1123assert.strictEqual(ext2.lastUsed, undefined); // No usage1124});11251126suite('Account entities query', () => {1127test('hasAnyUsage returns false for clean account', () => {1128const entitiesQuery = queryService.provider('github').account('[email protected]').entities();1129assert.strictEqual(entitiesQuery.hasAnyUsage(), false);1130});11311132test('hasAnyUsage returns true when extension has usage', () => {1133const accountQuery = queryService.provider('github').account('[email protected]');1134accountQuery.extension('test-ext').addUsage(['read'], 'Test Extension');11351136const entitiesQuery = accountQuery.entities();1137assert.strictEqual(entitiesQuery.hasAnyUsage(), true);1138});11391140test('hasAnyUsage returns true when MCP server has usage', () => {1141const accountQuery = queryService.provider('github').account('[email protected]');1142accountQuery.mcpServer('test-server').addUsage(['write'], 'Test Server');11431144const entitiesQuery = accountQuery.entities();1145assert.strictEqual(entitiesQuery.hasAnyUsage(), true);1146});11471148test('hasAnyUsage returns true when extension has access', () => {1149const accountQuery = queryService.provider('github').account('[email protected]');1150accountQuery.extension('test-ext').setAccessAllowed(true, 'Test Extension');11511152const entitiesQuery = accountQuery.entities();1153assert.strictEqual(entitiesQuery.hasAnyUsage(), true);1154});11551156test('hasAnyUsage returns true when MCP server has access', () => {1157const accountQuery = queryService.provider('github').account('[email protected]');1158accountQuery.mcpServer('test-server').setAccessAllowed(true, 'Test Server');11591160const entitiesQuery = accountQuery.entities();1161assert.strictEqual(entitiesQuery.hasAnyUsage(), true);1162});11631164test('getEntityCount returns correct counts', () => {1165const accountQuery = queryService.provider('github').account('[email protected]');11661167// Set up test data1168accountQuery.extension('ext1').setAccessAllowed(true, 'Extension One');1169accountQuery.extension('ext2').setAccessAllowed(true, 'Extension Two');1170accountQuery.mcpServer('server1').setAccessAllowed(true, 'Server One');11711172const entitiesQuery = accountQuery.entities();1173const counts = entitiesQuery.getEntityCount();11741175assert.strictEqual(counts.extensions, 2);1176assert.strictEqual(counts.mcpServers, 1);1177assert.strictEqual(counts.total, 3);1178});11791180test('getEntityCount returns zero for clean account', () => {1181const entitiesQuery = queryService.provider('github').account('[email protected]').entities();1182const counts = entitiesQuery.getEntityCount();11831184assert.strictEqual(counts.extensions, 0);1185assert.strictEqual(counts.mcpServers, 0);1186assert.strictEqual(counts.total, 0);1187});11881189test('removeAllAccess removes access for all entity types', () => {1190const accountQuery = queryService.provider('github').account('[email protected]');11911192// Set up test data1193accountQuery.extension('ext1').setAccessAllowed(true, 'Extension One');1194accountQuery.extension('ext2').setAccessAllowed(true, 'Extension Two');1195accountQuery.mcpServer('server1').setAccessAllowed(true, 'Server One');1196accountQuery.mcpServer('server2').setAccessAllowed(true, 'Server Two');11971198// Verify initial state1199assert.strictEqual(accountQuery.extension('ext1').isAccessAllowed(), true);1200assert.strictEqual(accountQuery.extension('ext2').isAccessAllowed(), true);1201assert.strictEqual(accountQuery.mcpServer('server1').isAccessAllowed(), true);1202assert.strictEqual(accountQuery.mcpServer('server2').isAccessAllowed(), true);12031204// Remove all access1205const entitiesQuery = accountQuery.entities();1206entitiesQuery.removeAllAccess();12071208// Verify all access is removed1209assert.strictEqual(accountQuery.extension('ext1').isAccessAllowed(), false);1210assert.strictEqual(accountQuery.extension('ext2').isAccessAllowed(), false);1211assert.strictEqual(accountQuery.mcpServer('server1').isAccessAllowed(), false);1212assert.strictEqual(accountQuery.mcpServer('server2').isAccessAllowed(), false);1213});12141215test('forEach iterates over all entity types', () => {1216const accountQuery = queryService.provider('github').account('[email protected]');12171218// Set up test data1219accountQuery.extension('ext1').setAccessAllowed(true, 'Extension One');1220accountQuery.extension('ext2').addUsage(['read'], 'Extension Two');1221accountQuery.mcpServer('server1').setAccessAllowed(true, 'Server One');1222accountQuery.mcpServer('server2').addUsage(['write'], 'Server Two');12231224const entitiesQuery = accountQuery.entities();1225const visitedEntities: Array<{ id: string; type: 'extension' | 'mcpServer' }> = [];12261227entitiesQuery.forEach((entityId, entityType) => {1228visitedEntities.push({ id: entityId, type: entityType });1229});12301231// Should visit all entities that have usage or access1232assert.strictEqual(visitedEntities.length, 4);12331234const extensions = visitedEntities.filter(e => e.type === 'extension');1235const mcpServers = visitedEntities.filter(e => e.type === 'mcpServer');12361237assert.strictEqual(extensions.length, 2);1238assert.strictEqual(mcpServers.length, 2);12391240// Check specific entities were visited1241assert.ok(visitedEntities.some(e => e.id === 'ext1' && e.type === 'extension'));1242assert.ok(visitedEntities.some(e => e.id === 'ext2' && e.type === 'extension'));1243assert.ok(visitedEntities.some(e => e.id === 'server1' && e.type === 'mcpServer'));1244assert.ok(visitedEntities.some(e => e.id === 'server2' && e.type === 'mcpServer'));1245});1246});1247});124812491250