/** * Tests for Profile Mapper * Verifies syncable profile detection and ClaudeKey mapping. */ import * as assert from 'assert'; const fs = require('fs'); describe('Profile Mapper', () => { const profileMapper = require('../../../dist/cliproxy/sync/profile-mapper'); const profileReader = require('../../../dist/api/services/profile-reader'); describe('mapProfileToClaudeKey', () => { it('returns null when env is missing', () => { const profile = { name: 'test', settingsPath: '/path', isConfigured: true }; const result = profileMapper.mapProfileToClaudeKey(profile); assert.strictEqual(result, null); }); it('returns null when ANTHROPIC_AUTH_TOKEN is missing', () => { const profile = { name: 'test', settingsPath: '/path', isConfigured: true, env: { ANTHROPIC_BASE_URL: 'https://example.com' }, }; const result = profileMapper.mapProfileToClaudeKey(profile); assert.strictEqual(result, null); }); it('generates ClaudeKey with correct prefix', () => { const profile = { name: 'glm', settingsPath: '/path', isConfigured: true, env: { ANTHROPIC_AUTH_TOKEN: 'sk-test-key', ANTHROPIC_BASE_URL: 'https://api.example.com', ANTHROPIC_MODEL: 'gpt-4', }, }; const result = profileMapper.mapProfileToClaudeKey(profile); assert.ok(result, 'Should return ClaudeKey'); assert.strictEqual(result['api-key'], 'sk-test-key'); assert.strictEqual(result.prefix, 'glm-'); assert.strictEqual(result['base-url'], 'https://api.example.com'); assert.ok(result.models, 'Should have models'); assert.strictEqual(result.models[0].name, 'gpt-4'); }); it('handles special characters in profile name', () => { const profile = { name: 'my@profile!', settingsPath: '/path', isConfigured: true, env: { ANTHROPIC_AUTH_TOKEN: 'sk-key' }, }; const result = profileMapper.mapProfileToClaudeKey(profile); assert.ok(result); assert.strictEqual(result.prefix, 'my-profile--'); }); it('omits base-url when not provided', () => { const profile = { name: 'test', settingsPath: '/path', isConfigured: true, env: { ANTHROPIC_AUTH_TOKEN: 'sk-key' }, }; const result = profileMapper.mapProfileToClaudeKey(profile); assert.ok(result); assert.strictEqual(result['base-url'], undefined); }); }); describe('loadSyncableProfiles', () => { it('returns an array', () => { const result = profileMapper.loadSyncableProfiles(); assert.ok(Array.isArray(result), 'Should return an array'); }); it('filters out profiles with placeholder tokens', () => { // loadSyncableProfiles reads from disk, just verify it doesn't throw // and returns array (actual filtering tested via integration) const result = profileMapper.loadSyncableProfiles(); assert.ok(Array.isArray(result)); }); it('uses profile-provided settingsPath instead of reconstructing from profile name', () => { const originalListApiProfiles = profileReader.listApiProfiles; const originalExistsSync = fs.existsSync; const originalReadFileSync = fs.readFileSync; const customSettingsPath = '/tmp/custom-sync-path.settings.json'; const readPaths: string[] = []; try { profileReader.listApiProfiles = () => ({ profiles: [ { name: 'glm', settingsPath: customSettingsPath, isConfigured: true, configSource: 'legacy', target: 'claude', }, ], variants: [], }); fs.existsSync = (filePath: string) => filePath === customSettingsPath; fs.readFileSync = (filePath: string) => { readPaths.push(filePath); return JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-test-key', }, }); }; const result = profileMapper.loadSyncableProfiles(); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].settingsPath, customSettingsPath); assert.deepStrictEqual(readPaths, [customSettingsPath]); } finally { profileReader.listApiProfiles = originalListApiProfiles; fs.existsSync = originalExistsSync; fs.readFileSync = originalReadFileSync; } }); it('skips profiles pinned to non-claude targets during local sync mapping', () => { const originalListApiProfiles = profileReader.listApiProfiles; const originalExistsSync = fs.existsSync; const originalReadFileSync = fs.readFileSync; const claudePath = '/tmp/claude-target.settings.json'; const droidPath = '/tmp/droid-target.settings.json'; const readPaths: string[] = []; try { profileReader.listApiProfiles = () => ({ profiles: [ { name: 'claude-profile', settingsPath: claudePath, isConfigured: true, configSource: 'legacy', target: 'claude', }, { name: 'droid-profile', settingsPath: droidPath, isConfigured: true, configSource: 'legacy', target: 'droid', }, ], variants: [], }); fs.existsSync = (filePath: string) => filePath === claudePath || filePath === droidPath; fs.readFileSync = (filePath: string) => { readPaths.push(filePath); return JSON.stringify({ env: { ANTHROPIC_AUTH_TOKEN: 'sk-test-key', }, }); }; const result = profileMapper.loadSyncableProfiles(); assert.strictEqual(result.length, 1); assert.strictEqual(result[0].name, 'claude-profile'); assert.deepStrictEqual(readPaths, [claudePath]); } finally { profileReader.listApiProfiles = originalListApiProfiles; fs.existsSync = originalExistsSync; fs.readFileSync = originalReadFileSync; } }); }); describe('generateSyncPayload', () => { it('returns an array of ClaudeKey objects', () => { const result = profileMapper.generateSyncPayload(); assert.ok(Array.isArray(result)); // Each item should have api-key if present for (const key of result) { assert.ok(key['api-key'], 'Each key should have api-key'); assert.ok(key.prefix, 'Each key should have prefix'); } }); }); describe('generateSyncPreview', () => { it('returns an array of preview items', () => { const result = profileMapper.generateSyncPreview(); assert.ok(Array.isArray(result)); for (const item of result) { assert.ok(typeof item.name === 'string', 'Each item should have name'); } }); }); describe('getSyncableProfileCount', () => { it('returns a number', () => { const result = profileMapper.getSyncableProfileCount(); assert.ok(typeof result === 'number'); assert.ok(result >= 0); }); }); });