mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
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)
This commit is contained in:
1 parent
4386e9122d
commit
43f1a9890e
1 file changed
+352
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user