Files
ccs/tests/unit/auth/profile-detector.test.ts
T
kaitranntt f2a4200625 fix(core): address all code review issues from PR #199
Critical fixes:
- CRIT-1: Fix placeholder detection case sensitivity in api-key-validator
- CRIT-2: Add TOCTOU protection via loadMigrationCheckData() for atomic config reads
- CRIT-3: Restore fs.existsSync mock in profile-detector tests

High priority fixes:
- H1-H3: API validator HTTP/HTTPS support, timeout abort, debug log
- H4-H6: OAuth explicit flow type, signal handling, TTY detection
- H7-H8: Profile collision warning, config-first save order
- H9-H11: expandPath consistency, preset sync CLI/UI
2025-12-24 20:18:57 -05:00

158 lines
5.4 KiB
TypeScript

/**
* Unit tests for Profile Detector
*/
import { describe, it, expect, beforeEach, afterEach, spyOn } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import ProfileDetector, { loadSettingsFromFile } from '../../../src/auth/profile-detector';
import * as unifiedConfigLoader from '../../../src/config/unified-config-loader';
describe('ProfileDetector', () => {
const tempDir = path.join(os.tmpdir(), `ccs-test-profile-detector-${process.pid}`);
beforeEach(() => {
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
});
afterEach(() => {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
describe('loadSettingsFromFile', () => {
it('should load settings from a valid JSON file', () => {
const settingsPath = path.join(tempDir, 'valid.settings.json');
const settings = { env: { KEY: 'VALUE' } };
fs.writeFileSync(settingsPath, JSON.stringify(settings));
const result = loadSettingsFromFile(settingsPath);
expect(result).toEqual({ KEY: 'VALUE' });
});
it('should return empty object for non-existent file', () => {
const result = loadSettingsFromFile(path.join(tempDir, 'non-existent.json'));
expect(result).toEqual({});
});
it('should return empty object for invalid JSON', () => {
const settingsPath = path.join(tempDir, 'invalid.json');
fs.writeFileSync(settingsPath, 'invalid json');
const result = loadSettingsFromFile(settingsPath);
expect(result).toEqual({});
});
it('should handle tilde expansion correctly', () => {
const mockHome = path.join(tempDir, 'home');
fs.mkdirSync(mockHome, { recursive: true });
const settingsPath = '~/test.settings.json';
const actualPath = path.join(mockHome, 'test.settings.json');
fs.writeFileSync(actualPath, JSON.stringify({ env: { HOME_VAR: 'TRUE' } }));
// Mock os.homedir
const homedirSpy = spyOn(os, 'homedir').mockReturnValue(mockHome);
try {
const result = loadSettingsFromFile(settingsPath);
expect(result).toEqual({ HOME_VAR: 'TRUE' });
} finally {
homedirSpy.mockRestore();
}
});
it('should handle env var expansion correctly', () => {
const settingsPath = path.join(tempDir, '${TEST_VAR}.json');
const actualPath = path.join(tempDir, 'actual.json');
fs.writeFileSync(actualPath, JSON.stringify({ env: { ENV_VAR: 'EXPANDED' } }));
process.env.TEST_VAR = 'actual';
try {
const result = loadSettingsFromFile(settingsPath);
expect(result).toEqual({ ENV_VAR: 'EXPANDED' });
} finally {
delete process.env.TEST_VAR;
}
});
});
describe('detectProfileType', () => {
let detector: ProfileDetector;
beforeEach(() => {
detector = new ProfileDetector();
});
it('should detect CLIProxy profiles', () => {
const result = detector.detectProfileType('gemini');
expect(result.type).toBe('cliproxy');
expect(result.name).toBe('gemini');
expect(result.provider).toBe('gemini');
});
it('should detect settings-based profile from unified config', () => {
const settingsPath = path.join(tempDir, 'glm.settings.json');
fs.writeFileSync(settingsPath, JSON.stringify({ env: { ANTHROPIC_MODEL: 'glm-4' } }));
const mockUnifiedConfig = {
version: 2,
profiles: {
glm: { settings: settingsPath, type: 'api' }
}
};
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(mockUnifiedConfig as any);
try {
const result = detector.detectProfileType('glm');
expect(result.type).toBe('settings');
expect(result.name).toBe('glm');
expect(result.env).toEqual({ ANTHROPIC_MODEL: 'glm-4' });
} finally {
isUnifiedModeSpy.mockRestore();
loadUnifiedConfigSpy.mockRestore();
}
});
it('should detect account-based profile from unified config', () => {
const mockUnifiedConfig = {
version: 2,
accounts: {
work: { created: '2025-01-01', last_used: '2025-01-02' }
}
};
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(mockUnifiedConfig as any);
try {
const result = detector.detectProfileType('work');
expect(result.type).toBe('account');
expect(result.name).toBe('work');
expect(result.profile).toBeDefined();
expect((result.profile as any).type).toBe('account');
} finally {
isUnifiedModeSpy.mockRestore();
loadUnifiedConfigSpy.mockRestore();
}
});
it('should return null for unknown profile (throws error)', () => {
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(false);
// Mock readConfig/readProfiles to return empty
const existsSyncSpy = spyOn(fs, 'existsSync').mockReturnValue(false);
try {
expect(() => detector.detectProfileType('unknown')).toThrow(/Profile not found/);
} finally {
isUnifiedModeSpy.mockRestore();
existsSyncSpy.mockRestore();
}
});
});
});