mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-19 20:14:43 +00:00
fix(codex-auth): fail fast on missing active profile
This commit is contained in:
1 parent
ea421c4a20
commit
08fe63c626
8 files changed
+326
-97
No files matched your search
@@ -9,7 +9,7 @@
|
||||
* Cases:
|
||||
* - Empty registry → resolveActiveProfile returns null (legacy mode)
|
||||
* - Missing registry file → returns null (no registry = legacy mode)
|
||||
* - CCS_CODEX_PROFILE set but registry missing → returns null + stderr warning
|
||||
* - CCS_CODEX_PROFILE set but registry missing or unmatched → throws to avoid unsafe fallback
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
@@ -74,7 +74,7 @@ describe('legacy fallback — empty registry', () => {
|
||||
});
|
||||
|
||||
describe('legacy fallback — CCS_CODEX_PROFILE set but no matching profile', () => {
|
||||
it('returns null and emits warning when env points to non-existent profile', async () => {
|
||||
it('throws when env points to non-existent profile', async () => {
|
||||
// Create registry with no profiles
|
||||
const registryPath = path.join(ccsHome, '.ccs', 'codex-profiles.yaml');
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
@@ -82,29 +82,19 @@ describe('legacy fallback — CCS_CODEX_PROFILE set but no matching profile', ()
|
||||
mode: 0o600,
|
||||
});
|
||||
|
||||
const stderrLines: string[] = [];
|
||||
const origWrite = process.stderr.write.bind(process.stderr);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
process.stderr.write = (chunk: any): boolean => {
|
||||
stderrLines.push(String(chunk));
|
||||
return true;
|
||||
};
|
||||
const { resolveActiveProfile } = await import('../../../src/codex-auth/resolve-active-profile');
|
||||
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost-profile' })).toThrow(
|
||||
/ghost-profile/
|
||||
);
|
||||
});
|
||||
|
||||
let result;
|
||||
try {
|
||||
const { resolveActiveProfile } = await import(
|
||||
'../../../src/codex-auth/resolve-active-profile'
|
||||
);
|
||||
result = resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost-profile' });
|
||||
} finally {
|
||||
process.stderr.write = origWrite;
|
||||
}
|
||||
it('throws when env is set but registry file is missing', async () => {
|
||||
const registryPath = path.join(ccsHome, '.ccs', 'codex-profiles.yaml');
|
||||
expect(fs.existsSync(registryPath)).toBe(false);
|
||||
|
||||
// Should fall back to null (not throw)
|
||||
expect(result).toBeNull();
|
||||
|
||||
// Warning emitted to stderr about missing profile
|
||||
const allStderr = stderrLines.join('');
|
||||
expect(allStderr).toContain('ghost-profile');
|
||||
const { resolveActiveProfile } = await import('../../../src/codex-auth/resolve-active-profile');
|
||||
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost-profile' })).toThrow(
|
||||
/does not exist/
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -143,6 +143,37 @@ describe('codex-runtime router — non-auth profile resolution', () => {
|
||||
expect(code).toBe(-1); // CCS branch: entry must not call process.exit()
|
||||
});
|
||||
|
||||
it('fails fast when CCS_CODEX_PROFILE points to a missing registry profile', async () => {
|
||||
writeRegistry({
|
||||
version: '1.0',
|
||||
default: null,
|
||||
profiles: {},
|
||||
});
|
||||
process.env.CCS_CODEX_PROFILE = 'ghost';
|
||||
|
||||
const stderrMessages: string[] = [];
|
||||
const origWrite = process.stderr.write.bind(process.stderr);
|
||||
process.stderr.write = (chunk: string | Uint8Array): boolean => {
|
||||
stderrMessages.push(typeof chunk === 'string' ? chunk : String(chunk));
|
||||
return true;
|
||||
};
|
||||
|
||||
try {
|
||||
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
|
||||
flushRouterCache();
|
||||
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
|
||||
|
||||
const { main } = require(routerPath) as { main: (argv: string[]) => Promise<number> };
|
||||
const code = await main(['node', 'codex-runtime', 'chat']);
|
||||
|
||||
expect(code).toBe(1);
|
||||
expect(process.env.CODEX_HOME).toBeUndefined();
|
||||
expect(stderrMessages.join('')).toContain("CCS_CODEX_PROFILE='ghost'");
|
||||
} finally {
|
||||
process.stderr.write = origWrite;
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves an explicit CODEX_HOME already in env — does not overwrite', async () => {
|
||||
const explicitHome = path.join(tempDir, 'explicit-codex-home');
|
||||
fs.mkdirSync(explicitHome, { recursive: true });
|
||||
|
||||
@@ -78,6 +78,15 @@ describe('resolveActiveProfile', () => {
|
||||
expect(stderrMessages.some((m) => m.includes('codex-auth'))).toBe(true);
|
||||
});
|
||||
|
||||
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 });
|
||||
|
||||
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: 'work' })).toThrow(
|
||||
/Refusing to fall back to ~\/\.codex/
|
||||
);
|
||||
});
|
||||
|
||||
it('returns source=env when CCS_CODEX_PROFILE matches a registry entry', () => {
|
||||
const profileDir = makeProfileDir('work');
|
||||
writeRegistry({
|
||||
@@ -132,28 +141,20 @@ describe('resolveActiveProfile', () => {
|
||||
expect(result?.source).toBe('env');
|
||||
});
|
||||
|
||||
it('returns null and warns when CCS_CODEX_PROFILE names a profile not in registry', () => {
|
||||
it('throws when CCS_CODEX_PROFILE names a profile not in registry', () => {
|
||||
writeRegistry({
|
||||
version: '1.0',
|
||||
default: null,
|
||||
profiles: {},
|
||||
});
|
||||
|
||||
const stderrMessages: string[] = [];
|
||||
const origWrite = process.stderr.write.bind(process.stderr);
|
||||
const spy = spyOn(process.stderr, 'write').mockImplementation(
|
||||
(msg: string | Uint8Array, ...rest: unknown[]) => {
|
||||
stderrMessages.push(typeof msg === 'string' ? msg : String(msg));
|
||||
return origWrite(msg as string, ...(rest as Parameters<typeof origWrite>).slice(1));
|
||||
}
|
||||
expect(() => resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost' })).toThrow(
|
||||
/CCS_CODEX_PROFILE='ghost'/
|
||||
);
|
||||
});
|
||||
|
||||
const result = resolveActiveProfile({ CCS_CODEX_PROFILE: 'ghost' });
|
||||
|
||||
spy.mockRestore();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(stderrMessages.some((m) => m.includes('ghost'))).toBe(true);
|
||||
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', () => {
|
||||
|
||||
Reference in new issue
Block a user