mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 18:16:08 +00:00
- resolve settings file from profile.settingsPath instead of reconstructing by name - skip non-claude targets for local claude-api-key sync semantics - add mapper regression tests for both behaviors
217 lines
7.1 KiB
TypeScript
217 lines
7.1 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|
|
});
|