Files
ccs/tests/unit/codex-auth/resolve-active-profile.test.ts

299 lines
9.0 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as yaml from 'js-yaml';
let resolveActiveProfile: (
env?: NodeJS.ProcessEnv
) => { name: string; dir: string; source: 'env' | 'default' } | null;
const ORIGINAL_CCS_HOME = process.env.CCS_HOME;
let tempDir: string;
let ccsHome: string;
let registryPath: string;
let instancesDir: string;
beforeEach(async () => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolve-profile-test-'));
ccsHome = path.join(tempDir, 'ccs');
fs.mkdirSync(path.join(ccsHome, '.ccs'), { recursive: true, mode: 0o700 });
process.env.CCS_HOME = ccsHome;
registryPath = path.join(ccsHome, '.ccs', 'codex-profiles.yaml');
instancesDir = path.join(ccsHome, '.ccs', 'codex-instances');
// Re-import after setting CCS_HOME so module picks up updated dir
const mod = await import('../../../src/codex-auth/resolve-active-profile');
resolveActiveProfile = mod.resolveActiveProfile;
});
afterEach(() => {
if (ORIGINAL_CCS_HOME === undefined) {
delete process.env.CCS_HOME;
} else {
process.env.CCS_HOME = ORIGINAL_CCS_HOME;
}
fs.rmSync(tempDir, { recursive: true, force: true });
});
// Helper to write a registry YAML fixture
function writeRegistry(data: object): void {
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
fs.writeFileSync(registryPath, yaml.dump(data, { indent: 2 }), { mode: 0o600 });
}
function makeProfileDir(name: string): string {
const dir = path.join(instancesDir, name);
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
return dir;
}
describe('resolveActiveProfile', () => {
it('returns null silently when registry file does not exist', () => {
// No registry written
const result = resolveActiveProfile({});
expect(result).toBeNull();
});
it('throws when registry YAML is corrupt even without an env override', () => {
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
fs.writeFileSync(registryPath, '{ invalid yaml: [[[', { mode: 0o600 });
expect(() => resolveActiveProfile({})).toThrow(/registry YAML could not be parsed/);
try {
resolveActiveProfile({});
} catch (err) {
expect(String(err)).toContain('$CCS_HOME/.ccs/codex-profiles.yaml');
expect(String(err)).not.toContain(registryPath);
expect(String(err)).not.toContain('invalid yaml');
}
});
it('throws when CCS_CODEX_PROFILE is set and registry YAML is corrupt', () => {
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
fs.writeFileSync(registryPath, '{ invalid yaml: [[[', { mode: 0o600 });
let thrown: unknown;
try {
resolveActiveProfile({ CCS_CODEX_PROFILE: 'work' });
} catch (err) {
thrown = err;
}
expect(thrown).toBeDefined();
expect(String(thrown)).toContain('Refusing to fall back to ~/.codex');
expect(String(thrown)).toContain('$CCS_HOME/.ccs/codex-profiles.yaml');
expect(String(thrown)).not.toContain(registryPath);
expect(String(thrown)).not.toContain('invalid yaml');
});
it('throws when registry is missing a valid profiles map', () => {
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
fs.writeFileSync(registryPath, 'version: "1.0"\ndefault: null\nprofiles: []\n', {
mode: 0o600,
});
expect(() => resolveActiveProfile({})).toThrow(/object profiles map/);
});
it('throws instead of falling back when registry default has an invalid falsy type', () => {
writeRegistry({
version: '1.0',
default: false,
profiles: {},
});
expect(() => resolveActiveProfile({})).toThrow(/default must be a string or null/i);
});
it('throws when CCS_CODEX_PROFILE contains an unsafe profile name', () => {
writeRegistry({
version: '1.0',
default: null,
profiles: {},
});
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: '../escape' })).toThrow(
/invalid.*path separators/i
);
});
it('throws when registry default contains an unsafe profile name', () => {
writeRegistry({
version: '1.0',
default: '../escape',
profiles: {
'../escape': { type: 'codex', created: '2026-01-01T00:00:00.000Z', last_used: null },
},
});
expect(() => resolveActiveProfile({})).toThrow(/invalid.*path separators/i);
});
it('throws when registry default points to a missing profile', () => {
writeRegistry({
version: '1.0',
default: 'ghost',
profiles: {},
});
expect(() => resolveActiveProfile({})).toThrow(/default profile is missing from profiles map/i);
});
it('throws when matched registry profile entry is malformed', () => {
writeRegistry({
version: '1.0',
default: 'work',
profiles: {
work: 1,
},
});
expect(() => resolveActiveProfile({})).toThrow(/profile "work" must be an object/i);
});
it('throws when a registry profile is missing required metadata', () => {
writeRegistry({
version: '1.0',
default: 'work',
profiles: {
work: { type: 'codex', last_used: null },
},
});
expect(() => resolveActiveProfile({})).toThrow(/created timestamp/i);
});
it('throws when a registry profile has invalid optional metadata types', () => {
writeRegistry({
version: '1.0',
default: 'work',
profiles: {
work: {
type: 'codex',
created: '2026-01-01T00:00:00.000Z',
last_used: null,
account_id: 123,
},
},
});
expect(() => resolveActiveProfile({})).toThrow(/account_id must be a string/i);
});
it('returns source=env when CCS_CODEX_PROFILE matches a registry entry', () => {
const profileDir = makeProfileDir('work');
writeRegistry({
version: '1.0',
default: null,
profiles: {
work: { type: 'codex', created: '2026-01-01T00:00:00.000Z', last_used: null },
},
});
const result = resolveActiveProfile({ CCS_CODEX_PROFILE: 'work' });
expect(result).not.toBeNull();
expect(result?.name).toBe('work');
expect(result?.source).toBe('env');
expect(result?.dir).toBe(profileDir);
});
it('returns source=default when no env var and registry has a default profile', () => {
const profileDir = makeProfileDir('personal');
writeRegistry({
version: '1.0',
default: 'personal',
profiles: {
personal: { type: 'codex', created: '2026-01-01T00:00:00.000Z', last_used: null },
},
});
const result = resolveActiveProfile({});
expect(result).not.toBeNull();
expect(result?.name).toBe('personal');
expect(result?.source).toBe('default');
expect(result?.dir).toBe(profileDir);
});
it('env > default precedence: CCS_CODEX_PROFILE overrides registry default', () => {
makeProfileDir('work');
makeProfileDir('personal');
writeRegistry({
version: '1.0',
default: 'personal',
profiles: {
work: { type: 'codex', created: '2026-01-01T00:00:00.000Z', last_used: null },
personal: { type: 'codex', created: '2026-01-01T00:00:00.000Z', last_used: null },
},
});
const result = resolveActiveProfile({ CCS_CODEX_PROFILE: 'work' });
expect(result?.name).toBe('work');
expect(result?.source).toBe('env');
});
it('throws when CCS_CODEX_PROFILE names a profile not in registry', () => {
writeRegistry({
version: '1.0',
default: null,
profiles: {},
});
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost' })).toThrow(
/CCS_CODEX_PROFILE='ghost'/
);
});
it('throws when CCS_CODEX_PROFILE is set but registry file is missing', () => {
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost' })).toThrow(/does not exist/);
});
it('treats empty/whitespace-only CCS_CODEX_PROFILE as unset, falls back to default', () => {
makeProfileDir('default-profile');
writeRegistry({
version: '1.0',
default: 'default-profile',
profiles: {
'default-profile': {
type: 'codex',
created: '2026-01-01T00:00:00.000Z',
last_used: null,
},
},
});
const resultEmpty = resolveActiveProfile({ CCS_CODEX_PROFILE: '' });
expect(resultEmpty?.name).toBe('default-profile');
expect(resultEmpty?.source).toBe('default');
const resultWhitespace = resolveActiveProfile({ CCS_CODEX_PROFILE: ' ' });
expect(resultWhitespace?.name).toBe('default-profile');
expect(resultWhitespace?.source).toBe('default');
});
it('resolves the profile dir to an absolute path', () => {
makeProfileDir('absolute-test');
writeRegistry({
version: '1.0',
default: 'absolute-test',
profiles: {
'absolute-test': {
type: 'codex',
created: '2026-01-01T00:00:00.000Z',
last_used: null,
},
},
});
const result = resolveActiveProfile({});
expect(result?.dir).toBe(path.resolve(result?.dir ?? ''));
expect(path.isAbsolute(result?.dir ?? '')).toBe(true);
});
});