mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 14:21:20 +00:00
Adds the storage substrate for ccsx auth profile isolation. Each Codex profile gets its own CODEX_HOME dir under ~/.ccs/codex-instances/<name>/ with isolated auth.json and history.jsonl; config.toml is shared via symlink to ~/.codex/config.toml so two terminals can run two Codex accounts simultaneously without duplicating user config. - CodexProfileRegistry (YAML, atomic write tmp.<pid>.<rand> + rename, orphan cleanup, full CRUD + default pointer) - decode-id-token: pure base64 JWT decoder for OpenAI id_token, reads nested https://api.openai.com/auth claims (chatgpt_plan_type, chatgpt_account_id) and dual-path email - ensureSharedConfigSymlink: self-healing, idempotent, overwrites stale entries with stderr warning - 45 unit tests, all green Foundation only — no CLI, no runtime injection, no dashboard. Subsequent commits wire those in.
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
|
|
let getCodexAuthRegistryPath: () => string;
|
|
let getCodexInstancesDir: () => string;
|
|
let resolveCodexProfileDir: (name: string) => string;
|
|
let getSharedCodexConfigPath: () => string;
|
|
|
|
const ORIGINAL_CCS_HOME = process.env.CCS_HOME;
|
|
|
|
beforeEach(async () => {
|
|
process.env.CCS_HOME = '/tmp/test-ccs-home';
|
|
// Re-import to pick up env change — use dynamic import with cache busting
|
|
const mod = await import('../../../src/codex-auth/codex-profile-paths');
|
|
getCodexAuthRegistryPath = mod.getCodexAuthRegistryPath;
|
|
getCodexInstancesDir = mod.getCodexInstancesDir;
|
|
resolveCodexProfileDir = mod.resolveCodexProfileDir;
|
|
getSharedCodexConfigPath = mod.getSharedCodexConfigPath;
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (ORIGINAL_CCS_HOME === undefined) {
|
|
delete process.env.CCS_HOME;
|
|
} else {
|
|
process.env.CCS_HOME = ORIGINAL_CCS_HOME;
|
|
}
|
|
});
|
|
|
|
describe('codex-profile-paths', () => {
|
|
it('getCodexAuthRegistryPath returns codex-profiles.yaml inside getCcsDir()', () => {
|
|
const result = getCodexAuthRegistryPath();
|
|
expect(result).toContain('codex-profiles.yaml');
|
|
expect(result).toContain('.ccs');
|
|
});
|
|
|
|
it('getCodexInstancesDir returns codex-instances inside getCcsDir()', () => {
|
|
const result = getCodexInstancesDir();
|
|
expect(result).toContain('codex-instances');
|
|
expect(result).toContain('.ccs');
|
|
});
|
|
|
|
it('resolveCodexProfileDir returns instancesDir/<name>', () => {
|
|
const instancesDir = getCodexInstancesDir();
|
|
const profileDir = resolveCodexProfileDir('work');
|
|
expect(profileDir).toBe(path.join(instancesDir, 'work'));
|
|
});
|
|
|
|
it('resolveCodexProfileDir correctly nests a different profile name', () => {
|
|
const instancesDir = getCodexInstancesDir();
|
|
const profileDir = resolveCodexProfileDir('personal');
|
|
expect(profileDir).toBe(path.join(instancesDir, 'personal'));
|
|
});
|
|
|
|
it('getSharedCodexConfigPath resolves under os.homedir() not getCcsDir()', () => {
|
|
const result = getSharedCodexConfigPath();
|
|
// Must equal os.homedir()/.codex/config.toml — uses real homedir, not getCcsDir()
|
|
expect(result).toBe(path.join(os.homedir(), '.codex', 'config.toml'));
|
|
// Must end with the Codex-canonical path fragment
|
|
expect(result).toMatch(/\.codex[/\\]config\.toml$/);
|
|
// Must NOT end inside the .ccs directory (i.e. not a CCS-owned path)
|
|
expect(result).not.toContain(path.join('.ccs', 'codex'));
|
|
});
|
|
});
|