Skip to content

Commit 56b963e

Browse files
committed
add tests
1 parent 2f73135 commit 56b963e

File tree

3 files changed

+169
-1
lines changed

3 files changed

+169
-1
lines changed

extension.bundle.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export * from './src/services/AzureResourcesService';
3333
export { createResourceGroup } from './src/commands/createResourceGroup';
3434
export * from './src/commands/deleteResourceGroup/v2/deleteResourceGroupV2';
3535
export { activate, deactivate } from './src/extension';
36+
// Export for testing only - not part of public API
37+
export { AuthAccountStateManager, getAuthAccountStateManager } from './src/exportAuthRecord';
3638
export * from './src/extensionVariables';
3739
export * from './src/hostapi.v2.internal';
3840
export * from './src/tree/azure/AzureResourceItem';

src/exportAuthRecord.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import { ext } from './extensionVariables';
1313

1414
/**
1515
* Thread-safe state manager for authentication accounts
16+
* @internal - Only exported for testing purposes
1617
*/
17-
class AuthAccountStateManager {
18+
export class AuthAccountStateManager {
1819
private static instance: AuthAccountStateManager;
1920
private accountsCache: readonly vscode.AuthenticationSessionAccountInformation[] = [];
2021
private isUpdating: boolean = false;
@@ -150,6 +151,7 @@ export function registerExportAuthRecordOnSessionChange(_context: ExtensionConte
150151
/**
151152
* Get the singleton instance of AuthAccountStateManager for managing authentication accounts state.
152153
* This provides thread-safe access to accounts fetched during auth record persistence.
154+
* @internal - Only exported for testing purposes
153155
*/
154156
export function getAuthAccountStateManager(): AuthAccountStateManager {
155157
return AuthAccountStateManager.getInstance();
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
7+
8+
import * as assert from 'assert';
9+
import { AuthAccountStateManager, getAuthAccountStateManager } from '../extension.bundle';
10+
11+
suite('AuthAccountStateManager Tests', () => {
12+
let stateManager: AuthAccountStateManager;
13+
14+
setup(() => {
15+
stateManager = getAuthAccountStateManager();
16+
// Clear cache before each test
17+
stateManager.clearCache();
18+
});
19+
20+
teardown(() => {
21+
// Clean up after each test
22+
stateManager.clearCache();
23+
});
24+
25+
test('getInstance returns singleton instance', () => {
26+
const instance1 = getAuthAccountStateManager();
27+
const instance2 = getAuthAccountStateManager();
28+
assert.strictEqual(instance1, instance2, 'Should return the same singleton instance');
29+
});
30+
31+
test('getCachedAccounts returns empty array initially', () => {
32+
const cachedAccounts = stateManager.getCachedAccounts();
33+
assert.strictEqual(cachedAccounts.length, 0, 'Should return empty array initially');
34+
});
35+
36+
test('clearCache resets the cached accounts', async () => {
37+
// First, fetch accounts to populate cache
38+
await stateManager.getAccounts('microsoft');
39+
40+
// Verify cache is populated (may be empty if no accounts, but should not throw)
41+
const cachedBefore = stateManager.getCachedAccounts();
42+
assert.ok(Array.isArray(cachedBefore), 'Cached accounts should be an array');
43+
44+
// Clear cache
45+
stateManager.clearCache();
46+
47+
// Verify cache is empty
48+
const cachedAfter = stateManager.getCachedAccounts();
49+
assert.strictEqual(cachedAfter.length, 0, 'Should return empty array after clearing cache');
50+
});
51+
52+
test('getAccounts returns accounts with change detection flags', async () => {
53+
const result = await stateManager.getAccounts('microsoft');
54+
55+
assert.ok(result, 'Result should be defined');
56+
assert.ok(Array.isArray(result.accounts), 'accounts should be an array');
57+
assert.strictEqual(typeof result.hasNewAccounts, 'boolean', 'hasNewAccounts should be a boolean');
58+
assert.strictEqual(typeof result.accountsRemoved, 'boolean', 'accountsRemoved should be a boolean');
59+
});
60+
61+
test('getAccounts caches accounts after first fetch', async () => {
62+
// First fetch
63+
const firstResult = await stateManager.getAccounts('microsoft');
64+
const firstCached = stateManager.getCachedAccounts();
65+
66+
// Verify cache is populated
67+
assert.strictEqual(firstCached.length, firstResult.accounts.length, 'Cache should match fetched accounts');
68+
69+
// Verify cached accounts match fetched accounts
70+
for (let i = 0; i < firstCached.length; i++) {
71+
assert.strictEqual(firstCached[i].id, firstResult.accounts[i].id, 'Cached account IDs should match');
72+
assert.strictEqual(firstCached[i].label, firstResult.accounts[i].label, 'Cached account labels should match');
73+
}
74+
});
75+
76+
test('getCachedAccounts returns a copy of the cache', () => {
77+
const cached1 = stateManager.getCachedAccounts();
78+
const cached2 = stateManager.getCachedAccounts();
79+
80+
// Both should be arrays
81+
assert.ok(Array.isArray(cached1), 'Should return an array');
82+
assert.ok(Array.isArray(cached2), 'Should return an array');
83+
84+
// They should have the same content but not be the same reference
85+
assert.notStrictEqual(cached1, cached2, 'Should return different array instances');
86+
});
87+
88+
test('getAccounts handles concurrent calls gracefully', async () => {
89+
// Make multiple concurrent calls
90+
const promises = [
91+
stateManager.getAccounts('microsoft'),
92+
stateManager.getAccounts('microsoft'),
93+
stateManager.getAccounts('microsoft')
94+
];
95+
96+
const results = await Promise.all(promises);
97+
98+
// All results should be defined
99+
results.forEach((result: Awaited<ReturnType<typeof stateManager.getAccounts>>, index: number) => {
100+
assert.ok(result, `Result ${index} should be defined`);
101+
assert.ok(Array.isArray(result.accounts), `Result ${index} accounts should be an array`);
102+
});
103+
104+
// All results should have the same account IDs
105+
const firstAccountIds = results[0].accounts.map((acc: { id: string }) => acc.id).sort();
106+
results.forEach((result: Awaited<ReturnType<typeof stateManager.getAccounts>>, index: number) => {
107+
const accountIds = result.accounts.map((acc: { id: string }) => acc.id).sort();
108+
assert.deepStrictEqual(accountIds, firstAccountIds, `Result ${index} should have the same account IDs`);
109+
});
110+
});
111+
112+
test('hasNewAccounts is true on first fetch with accounts', async function () {
113+
this.timeout(10000); // Increase timeout for authentication checks
114+
115+
// Clear cache to ensure fresh state
116+
stateManager.clearCache();
117+
118+
// First fetch
119+
const result = await stateManager.getAccounts('microsoft');
120+
121+
// If there are accounts, hasNewAccounts should be true on first fetch
122+
if (result.accounts.length > 0) {
123+
assert.strictEqual(result.hasNewAccounts, true, 'Should detect new accounts on first fetch when accounts exist');
124+
} else {
125+
// If no accounts exist, hasNewAccounts should be false
126+
assert.strictEqual(result.hasNewAccounts, false, 'Should not detect new accounts when no accounts exist');
127+
}
128+
});
129+
130+
test('hasNewAccounts is false on subsequent fetch with same accounts', async function () {
131+
this.timeout(10000); // Increase timeout for authentication checks
132+
133+
// First fetch to populate cache
134+
await stateManager.getAccounts('microsoft');
135+
136+
// Second fetch should not detect new accounts (assuming accounts haven't changed)
137+
const result = await stateManager.getAccounts('microsoft');
138+
139+
assert.strictEqual(result.hasNewAccounts, false, 'Should not detect new accounts on subsequent fetch');
140+
});
141+
142+
test('accountsRemoved is false when no accounts are removed', async function () {
143+
this.timeout(10000); // Increase timeout for authentication checks
144+
145+
// First fetch to populate cache
146+
await stateManager.getAccounts('microsoft');
147+
148+
// Second fetch should not detect removed accounts (assuming accounts haven't changed)
149+
const result = await stateManager.getAccounts('microsoft');
150+
151+
assert.strictEqual(result.accountsRemoved, false, 'Should not detect removed accounts when accounts remain the same');
152+
});
153+
154+
test('multiple calls to clearCache are safe', () => {
155+
// Clear multiple times
156+
stateManager.clearCache();
157+
stateManager.clearCache();
158+
stateManager.clearCache();
159+
160+
// Should still return empty array
161+
const cached = stateManager.getCachedAccounts();
162+
assert.strictEqual(cached.length, 0, 'Should return empty array after multiple clears');
163+
});
164+
});

0 commit comments

Comments
 (0)