From 43f1a9890e20387fdd8f059a778e345e9c3eacc2 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 1 Jan 2026 13:34:25 -0500 Subject: [PATCH] test(cliproxy): add unit tests for discoverExistingAccounts - Test kiro/ghcp provider type mapping - Test email extraction from filename fallback - Test multiple accounts per provider - Test edge cases (invalid JSON, missing type, unknown provider) --- .../cliproxy/account-manager-discover.test.js | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 tests/unit/cliproxy/account-manager-discover.test.js diff --git a/tests/unit/cliproxy/account-manager-discover.test.js b/tests/unit/cliproxy/account-manager-discover.test.js new file mode 100644 index 00000000..ae3cf72a --- /dev/null +++ b/tests/unit/cliproxy/account-manager-discover.test.js @@ -0,0 +1,352 @@ +/** + * Account Manager Discovery Tests + * + * Tests for discoverExistingAccounts() function including: + * - Kiro/GHCP provider mapping + * - Email extraction from filename + * - Multiple accounts per provider + */ + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +describe('Account Manager - discoverExistingAccounts', () => { + let testDir; + let originalCcsHome; + let accountManager; + + beforeEach(() => { + // Create temp directory for test + testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-test-')); + const ccsDir = path.join(testDir, '.ccs', 'cliproxy'); + const authDir = path.join(ccsDir, 'auth'); + fs.mkdirSync(authDir, { recursive: true }); + + // Mock CCS_HOME to use temp directory + originalCcsHome = process.env.CCS_HOME; + process.env.CCS_HOME = testDir; + + // Clear cache and reload + delete require.cache[require.resolve('../../../dist/cliproxy/account-manager')]; + delete require.cache[require.resolve('../../../dist/cliproxy/config-generator')]; + delete require.cache[require.resolve('../../../dist/utils/config-manager')]; + accountManager = require('../../../dist/cliproxy/account-manager'); + }); + + afterEach(() => { + // Restore CCS_HOME + if (originalCcsHome !== undefined) { + process.env.CCS_HOME = originalCcsHome; + } else { + delete process.env.CCS_HOME; + } + + // Cleanup temp directory + fs.rmSync(testDir, { recursive: true, force: true }); + }); + + function createAuthFile(filename, content) { + const authDir = path.join(testDir, '.ccs', 'cliproxy', 'auth'); + fs.writeFileSync(path.join(authDir, filename), JSON.stringify(content)); + } + + function getAccountsFile() { + const accountsPath = path.join(testDir, '.ccs', 'cliproxy', 'accounts.json'); + if (fs.existsSync(accountsPath)) { + return JSON.parse(fs.readFileSync(accountsPath, 'utf-8')); + } + return null; + } + + // ========================================================================= + // Provider Type Mapping Tests + // ========================================================================= + describe('Provider Type Mapping', () => { + it('discovers kiro accounts with type="kiro"', () => { + createAuthFile('kiro-google-user@example.com.json', { + type: 'kiro', + email: '', + provider: 'Google', + profile_arn: 'arn:aws:codewhisperer:us-east-1::profile/123', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert(accounts, 'accounts.json should be created'); + assert(accounts.providers.kiro, 'kiro provider should exist'); + assert.strictEqual( + Object.keys(accounts.providers.kiro.accounts).length, + 1, + 'Should have 1 kiro account' + ); + }); + + it('discovers kiro accounts with type="codewhisperer"', () => { + createAuthFile('codewhisperer-user@example.com.json', { + type: 'codewhisperer', + email: 'user@example.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert(accounts.providers.kiro, 'codewhisperer type should map to kiro provider'); + }); + + it('discovers ghcp accounts with type="github-copilot"', () => { + createAuthFile('ghcp-user@example.com.json', { + type: 'github-copilot', + email: 'user@example.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert(accounts.providers.ghcp, 'github-copilot type should map to ghcp provider'); + }); + + it('discovers ghcp accounts with type="copilot"', () => { + createAuthFile('copilot-user@example.com.json', { + type: 'copilot', + email: 'user@example.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert(accounts.providers.ghcp, 'copilot type should map to ghcp provider'); + }); + + it('handles case-insensitive type values', () => { + createAuthFile('kiro-test.json', { + type: 'KIRO', + email: 'test@example.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert(accounts.providers.kiro, 'KIRO (uppercase) should map to kiro provider'); + }); + }); + + // ========================================================================= + // Email Extraction Tests + // ========================================================================= + describe('Email Extraction from Filename', () => { + it('extracts email from filename when data.email is empty', () => { + createAuthFile('kiro-google-user@example.com.json', { + type: 'kiro', + email: '', + provider: 'Google', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + const accountIds = Object.keys(accounts.providers.kiro.accounts); + assert.strictEqual(accountIds[0], 'user@example.com', 'Should extract email from filename'); + }); + + it('uses data.email when available', () => { + createAuthFile('kiro-test.json', { + type: 'kiro', + email: 'actual@email.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + const accountIds = Object.keys(accounts.providers.kiro.accounts); + assert.strictEqual(accountIds[0], 'actual@email.com', 'Should use data.email when available'); + }); + + it('falls back to "default" when no email in file or filename', () => { + createAuthFile('kiro-nomail.json', { + type: 'kiro', + email: '', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + const accountIds = Object.keys(accounts.providers.kiro.accounts); + assert.strictEqual(accountIds[0], 'default', 'Should fall back to "default"'); + }); + + it('handles dots in email local part', () => { + createAuthFile('kiro-google-first.last@example.com.json', { + type: 'kiro', + email: '', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + const accountIds = Object.keys(accounts.providers.kiro.accounts); + assert.strictEqual( + accountIds[0], + 'first.last@example.com', + 'Should handle dots in local part' + ); + }); + + it('handles plus addressing in email', () => { + createAuthFile('kiro-google-user+tag@example.com.json', { + type: 'kiro', + email: '', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + const accountIds = Object.keys(accounts.providers.kiro.accounts); + assert.strictEqual(accountIds[0], 'user+tag@example.com', 'Should handle plus addressing'); + }); + }); + + // ========================================================================= + // Multiple Accounts Tests + // ========================================================================= + describe('Multiple Accounts', () => { + it('discovers multiple kiro accounts', () => { + createAuthFile('kiro-google-user1@example.com.json', { + type: 'kiro', + email: '', + }); + createAuthFile('kiro-google-user2@example.com.json', { + type: 'kiro', + email: '', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert.strictEqual( + Object.keys(accounts.providers.kiro.accounts).length, + 2, + 'Should discover 2 kiro accounts' + ); + }); + + it('does not overwrite existing accounts', () => { + // Create first account + createAuthFile('kiro-google-user1@example.com.json', { + type: 'kiro', + email: '', + }); + accountManager.discoverExistingAccounts(); + + // Add another file and run again + createAuthFile('kiro-google-user2@example.com.json', { + type: 'kiro', + email: '', + }); + + // Reload module to clear in-memory cache + delete require.cache[require.resolve('../../../dist/cliproxy/account-manager')]; + accountManager = require('../../../dist/cliproxy/account-manager'); + accountManager.discoverExistingAccounts(); + + const accounts = getAccountsFile(); + assert.strictEqual( + Object.keys(accounts.providers.kiro.accounts).length, + 2, + 'Should have 2 accounts without overwriting' + ); + }); + + it('sets first discovered account as default', () => { + createAuthFile('kiro-google-user@example.com.json', { + type: 'kiro', + email: '', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + assert.strictEqual( + accounts.providers.kiro.default, + 'user@example.com', + 'First account should be default' + ); + }); + }); + + // ========================================================================= + // Edge Cases + // ========================================================================= + describe('Edge Cases', () => { + it('skips files without type field', () => { + createAuthFile('invalid.json', { + email: 'test@example.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + // Should either be null or have no providers + if (accounts) { + assert.strictEqual(Object.keys(accounts.providers).length, 0, 'Should have no providers'); + } + }); + + it('skips files with unknown type', () => { + createAuthFile('unknown.json', { + type: 'unknown-provider', + email: 'test@example.com', + }); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + if (accounts) { + assert.strictEqual( + Object.keys(accounts.providers).length, + 0, + 'Should skip unknown providers' + ); + } + }); + + it('skips non-JSON files', () => { + fs.writeFileSync( + path.join(testDir, '.ccs', 'cliproxy', 'auth', 'readme.txt'), + 'This is not JSON' + ); + + accountManager.discoverExistingAccounts(); + const accounts = getAccountsFile(); + + // Should not throw, should skip the file + if (accounts) { + assert.strictEqual(Object.keys(accounts.providers).length, 0); + } + }); + + it('handles malformed JSON gracefully', () => { + fs.writeFileSync( + path.join(testDir, '.ccs', 'cliproxy', 'auth', 'bad.json'), + '{not valid json' + ); + + // Should not throw + assert.doesNotThrow(() => { + accountManager.discoverExistingAccounts(); + }); + }); + + it('handles missing auth directory', () => { + // Remove auth directory + fs.rmSync(path.join(testDir, '.ccs', 'cliproxy', 'auth'), { recursive: true }); + + // Should not throw + assert.doesNotThrow(() => { + accountManager.discoverExistingAccounts(); + }); + }); + }); +});