mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
The test's access stub matched a forward-slash suffix, so on Windows the resolver's backslash path never matched and it fell back to PATH. The stub and the expected value now use the same path.join result. The Windows CI job also runs this test file now. Co-authored-by: Sameh Khamis <[email protected]>
277 lines
7.9 KiB
JavaScript
277 lines
7.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { EventEmitter } from 'node:events';
|
|
import test from 'node:test';
|
|
import path from 'node:path';
|
|
import {
|
|
codexAuthPath,
|
|
preferCodexOAuth,
|
|
readCodexOAuthAccessToken,
|
|
resolveCodexExecutable,
|
|
startCodexChatGptLogin,
|
|
} from '../server/providers/openai/codex-auth.js';
|
|
|
|
const nowMs = Date.UTC(2026, 8, 27);
|
|
const jwt = (payload) =>
|
|
`${Buffer.from('{}').toString('base64url')}.${Buffer.from(JSON.stringify(payload)).toString('base64url')}.signature`;
|
|
const auth = (token, extras = {}) =>
|
|
JSON.stringify({
|
|
auth_mode: 'chatgpt',
|
|
tokens: { access_token: token, refresh_token: 'refresh-fixture' },
|
|
...extras,
|
|
});
|
|
|
|
test('optional Codex OAuth pin fails closed on ambiguous values', () => {
|
|
assert.equal(preferCodexOAuth({}), false);
|
|
assert.equal(preferCodexOAuth({ GEV_PREFER_CODEX_OAUTH: 'true' }), true);
|
|
assert.equal(preferCodexOAuth({ GEV_PREFER_CODEX_OAUTH: 'off' }), false);
|
|
assert.throws(
|
|
() => preferCodexOAuth({ GEV_PREFER_CODEX_OAUTH: 'maybe' }),
|
|
/must be true or false/,
|
|
);
|
|
});
|
|
|
|
test('Codex auth path follows explicit auth file, CODEX_HOME, then home directory', () => {
|
|
assert.equal(
|
|
codexAuthPath({
|
|
environment: { CODEX_AUTH_JSON: '/tmp/custom-auth.json' },
|
|
home: '/home/fixture',
|
|
}),
|
|
path.resolve('/tmp/custom-auth.json'),
|
|
);
|
|
assert.equal(
|
|
codexAuthPath({
|
|
environment: { CODEX_HOME: '/tmp/codex-home' },
|
|
home: '/home/fixture',
|
|
}),
|
|
path.join(path.resolve('/tmp/codex-home'), 'auth.json'),
|
|
);
|
|
assert.equal(
|
|
codexAuthPath({ environment: {}, home: '/home/fixture' }),
|
|
path.join('/home/fixture', '.codex', 'auth.json'),
|
|
);
|
|
});
|
|
|
|
test('Codex OAuth reader accepts a current ChatGPT token and rejects unusable credentials', () => {
|
|
const current = jwt({ exp: nowMs / 1000 + 3600 });
|
|
assert.equal(
|
|
readCodexOAuthAccessToken({
|
|
authPath: '/unused',
|
|
nowMs,
|
|
readFile: () => auth(current),
|
|
}),
|
|
current,
|
|
);
|
|
assert.equal(
|
|
readCodexOAuthAccessToken({
|
|
authPath: '/unused',
|
|
nowMs,
|
|
readFile: () =>
|
|
JSON.stringify({
|
|
auth_mode: 'chatgpt',
|
|
tokens: { access_token: current },
|
|
}),
|
|
}),
|
|
current,
|
|
'GEV does not require or consume the vendor refresh token',
|
|
);
|
|
for (const readFile of [
|
|
() => '{bad json',
|
|
() => JSON.stringify({ tokens: {} }),
|
|
() => auth(current, { auth_mode: 'apikey' }),
|
|
() =>
|
|
JSON.stringify({
|
|
OPENAI_API_KEY: 'sk-fixture',
|
|
tokens: { access_token: current, refresh_token: 'refresh-fixture' },
|
|
}),
|
|
() => auth('opaque-token'),
|
|
() => auth('header.not-json.signature'),
|
|
() => auth(jwt({ exp: 'tomorrow' })),
|
|
() => {
|
|
throw new Error('missing');
|
|
},
|
|
]) {
|
|
assert.throws(
|
|
() => readCodexOAuthAccessToken({ authPath: '/unused', readFile, nowMs }),
|
|
/ChatGPT sign-in is unavailable/,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('Codex OAuth reader enforces expiry margin and bounds tokens without exp by file age', () => {
|
|
for (const exp of [nowMs / 1000 - 1, nowMs / 1000 + 59]) {
|
|
assert.throws(
|
|
() => {
|
|
readCodexOAuthAccessToken({
|
|
authPath: '/unused',
|
|
nowMs,
|
|
readFile: () => auth(jwt({ exp })),
|
|
});
|
|
},
|
|
(error) => {
|
|
assert.match(error.message, /sign-in has expired/);
|
|
assert.equal(error.code, 'CODEX_OAUTH_REAUTH_REQUIRED');
|
|
return true;
|
|
},
|
|
);
|
|
}
|
|
const noExp = jwt({ sub: 'fixture' });
|
|
const base = { authPath: '/unused', nowMs, readFile: () => auth(noExp) };
|
|
assert.equal(
|
|
readCodexOAuthAccessToken({
|
|
...base,
|
|
statFile: () => ({ mtimeMs: nowMs }),
|
|
}),
|
|
noExp,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
readCodexOAuthAccessToken({
|
|
...base,
|
|
statFile: () => ({ mtimeMs: nowMs - 3600_000 }),
|
|
}),
|
|
/sign-in has expired/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
readCodexOAuthAccessToken({
|
|
...base,
|
|
statFile: () => {
|
|
throw new Error('missing');
|
|
},
|
|
}),
|
|
/sign-in is unavailable/,
|
|
);
|
|
});
|
|
|
|
test('Codex login resolves an installed executable before PATH and starts browser auth detached', async () => {
|
|
const checked = [];
|
|
// Built with path.join so the stub matches the resolver on Windows too,
|
|
// where the separators are backslashes.
|
|
const installed = path.join('/home/fixture', '.local', 'bin', 'codex');
|
|
assert.equal(
|
|
resolveCodexExecutable({
|
|
environment: {},
|
|
home: '/home/fixture',
|
|
access(candidate) {
|
|
checked.push(candidate);
|
|
if (candidate === installed) return;
|
|
throw new Error('missing');
|
|
},
|
|
}),
|
|
installed,
|
|
);
|
|
assert.equal(checked.length, 1);
|
|
|
|
const child = new EventEmitter();
|
|
let unref = 0;
|
|
child.unref = () => {
|
|
unref += 1;
|
|
};
|
|
const calls = [];
|
|
const pending = startCodexChatGptLogin({
|
|
executable: '/fixture/codex',
|
|
home: '/home/fixture',
|
|
environment: {
|
|
PATH: '/fixture',
|
|
https_proxy: 'http://proxy.fixture:8080',
|
|
OPENAI_API_KEY: 'fixture-api-key',
|
|
GOOGLE_MAPS_SERVER_API_KEY: 'fixture-google-key',
|
|
OPENSKY_CLIENT_SECRET: 'fixture-opensky-secret',
|
|
},
|
|
spawnImpl(command, args, options) {
|
|
calls.push({ command, args, options });
|
|
queueMicrotask(() => child.emit('spawn'));
|
|
return child;
|
|
},
|
|
});
|
|
const login = await pending;
|
|
assert.equal(unref, 1);
|
|
assert.equal(calls[0].command, '/fixture/codex');
|
|
assert.deepEqual(calls[0].args, ['login']);
|
|
assert.equal(calls[0].options.detached, true);
|
|
assert.equal(calls[0].options.stdio, 'ignore');
|
|
assert.deepEqual(
|
|
calls[0].options.env,
|
|
{
|
|
PATH: '/fixture',
|
|
https_proxy: 'http://proxy.fixture:8080',
|
|
CODEX_HOME: path.join('/home/fixture', '.codex'),
|
|
},
|
|
'provider secrets stay out of the login process',
|
|
);
|
|
child.emit('exit', 1);
|
|
assert.deepEqual(await login.completion, { exitCode: 1 });
|
|
});
|
|
|
|
test('Codex login refuses a read/write storage mismatch before spawning', async () => {
|
|
await assert.rejects(
|
|
startCodexChatGptLogin({
|
|
environment: { CODEX_AUTH_JSON: '/tmp/another-auth.json' },
|
|
home: '/home/fixture',
|
|
executable: 'codex',
|
|
spawnImpl: () => assert.fail('mismatched storage must not launch login'),
|
|
}),
|
|
{ code: 'CODEX_OAUTH_STORAGE_MISMATCH' },
|
|
);
|
|
});
|
|
|
|
test('Codex login resolves relative CODEX_HOME before changing the child directory', async () => {
|
|
const child = new EventEmitter();
|
|
const environment = { CODEX_HOME: './fixture-codex' };
|
|
const login = await startCodexChatGptLogin({
|
|
environment,
|
|
executable: 'codex',
|
|
spawnImpl(_command, _args, options) {
|
|
assert.equal(
|
|
options.env.CODEX_HOME,
|
|
path.resolve(environment.CODEX_HOME),
|
|
);
|
|
queueMicrotask(() => child.emit('spawn'));
|
|
return child;
|
|
},
|
|
});
|
|
assert.equal(
|
|
environment.CODEX_HOME,
|
|
'./fixture-codex',
|
|
'caller environment is unchanged',
|
|
);
|
|
child.emit('exit', 0);
|
|
assert.deepEqual(await login.completion, { exitCode: 0 });
|
|
});
|
|
|
|
test('Codex login hides spawn errors and can cancel only its owned child', async () => {
|
|
const child = new EventEmitter();
|
|
await assert.rejects(
|
|
startCodexChatGptLogin({
|
|
executable: 'codex',
|
|
spawnImpl() {
|
|
queueMicrotask(() =>
|
|
child.emit('error', new Error('private-token-fixture')),
|
|
);
|
|
return child;
|
|
},
|
|
}),
|
|
(error) =>
|
|
error.code === 'CODEX_OAUTH_LOGIN_FAILED' &&
|
|
!error.message.includes('private-token-fixture'),
|
|
);
|
|
|
|
const running = new EventEmitter();
|
|
let cancelled = 0;
|
|
running.kill = (signal) => {
|
|
assert.equal(signal, 'SIGTERM');
|
|
cancelled += 1;
|
|
running.emit('exit', null);
|
|
};
|
|
const login = await startCodexChatGptLogin({
|
|
executable: 'codex',
|
|
spawnImpl() {
|
|
queueMicrotask(() => running.emit('spawn'));
|
|
return running;
|
|
},
|
|
});
|
|
login.cancel();
|
|
assert.equal(cancelled, 1);
|
|
assert.deepEqual(await login.completion, { exitCode: null });
|
|
});
|