// P4: callback must match an unfinished, unexpired login owned by this human/business. // Named mutant: skip pending-state check. Canary errors must never reflect upstream input. import test from 'node:test'; import assert from 'node:assert/strict'; const moduleURL = new URL('../src/proxy-page.mjs', import.meta.url); const owner = { business: 'demo', human: 'owner' }; async function setup(options = {}) { const { ProxyPage } = await import(moduleURL); let now = 1000; const calls = []; const client = Object.fromEntries(['getCredentials', 'getKeys', 'oauthStart', 'oauthCallback', 'oauthStatus', 'oauthCancel', 'getUpstreamGroups', 'putUpstreamGroups', 'patchCredential'].map(name => [name, async (...args) => { calls.push([name, ...args]); if (name === 'oauthStart') return { status: 'ok', state: 'p4-state', url: 'https://claude.ai/oauth/authorize?state=p4-state' }; if (name === 'oauthStatus') return { status: 'wait' }; if (name === 'getCredentials') return { files: [] }; if (name === 'getUpstreamGroups') return []; if (name === 'getKeys') return []; return { status: 'ok' }; }])); const p = new ProxyPage({ client, instanceTag: 'fixture', now: () => now, ...options }); return { client, calls, p, time: value => { now = value; }, call: (verb, args = {}, who = owner) => p.request(verb, args, who) }; } for (const [verb, args] of [ ['proxy.login.start', { provider: 'other' }], ['proxy.apikey.add', { provider: 'other', key: 'fixture-key' }], ]) test(`${verb}: provider-not-allowed`, async () => { const s = await setup(); await assert.rejects(s.call(verb, args), { code: 'provider-not-allowed' }); assert.equal(s.calls.length, 0); }); for (const [name, key] of [['empty', ''], ['too long', 'x'.repeat(4097)], ['charset', 'bad\nkey']]) test(`apikey-invalid ${name}`, async () => { const s = await setup(); await assert.rejects(s.call('proxy.apikey.add', { provider: 'z.ai', key }), { code: 'apikey-invalid' }); assert.equal(s.calls.length, 0); }); for (const [name, url] of [['not URL', 'not a url'], ['oversize', 'http://localhost/?' + 'a'.repeat(8193)], ['repeated state', 'http://localhost/?state=p4-state&state=p4-state']]) test(`callback-url-invalid ${name}`, async () => { const s = await setup(); await s.call('proxy.login.start', { provider: 'claude' }); await assert.rejects(s.call('proxy.login.submit', { redirect_url: url }), { code: 'callback-url-invalid' }); assert.equal(s.calls.filter(c => c[0] === 'oauthCallback').length, 0); }); test('login-state-unknown callback never reaches proxy', async () => { const s = await setup(); await assert.rejects(s.call('proxy.login.submit', { redirect_url: 'http://localhost/?state=unknown&code=p4-code' }), { code: 'login-state-unknown' }); assert.equal(s.calls.length, 0); }); test('login state is scoped to the human and business', async () => { const s = await setup(); await s.call('proxy.login.start', { provider: 'claude' }); for (const who of [{ business: 'other', human: 'owner' }, { business: 'demo', human: 'other' }]) await assert.rejects(s.call('proxy.login.status', { state: 'p4-state' }, who), { code: 'login-state-unknown' }); assert.equal(s.calls.length, 1); }); test('accepted callback wait is pending; only a status ok completes and retires state', async () => { const s = await setup(); await s.call('proxy.login.start', { provider: 'claude' }); assert.deepEqual(await s.call('proxy.login.submit', { redirect_url: 'http://localhost/?state=p4-state&code=p4-code' }), { status: 'wait' }); assert.deepEqual(await s.call('proxy.login.status', { state: 'p4-state' }), { status: 'wait' }); s.client.oauthStatus = async () => ({ status: 'ok', token: 'never-return' }); assert.deepEqual(await s.call('proxy.login.status', { state: 'p4-state' }), { status: 'ok' }); await assert.rejects(s.call('proxy.login.status', { state: 'p4-state' }), { code: 'login-state-unknown' }); await assert.rejects(s.call('proxy.login.submit', { redirect_url: 'http://localhost/?state=p4-state&code=p4-code' }), { code: 'login-state-unknown' }); }); test('expiry reports login-timeout once with no management call and retires state', async () => { const s = await setup(); await s.call('proxy.login.start', { provider: 'claude' }); s.time(301000); assert.deepEqual(await s.call('proxy.login.status', { state: 'p4-state' }), { status: 'error', reason: 'login-timeout' }); assert.equal(s.calls.length, 1); await assert.rejects(s.call('proxy.login.status', { state: 'p4-state' }), { code: 'login-state-unknown' }); }); test('cancel calls DELETE and retires state', async () => { const s = await setup(); await s.call('proxy.login.start', { provider: 'claude' }); assert.deepEqual(await s.call('proxy.login.cancel', { state: 'p4-state' }), { status: 'cancelled' }); assert.deepEqual(s.calls.at(-1), ['oauthCancel', 'p4-state']); await assert.rejects(s.call('proxy.login.status', { state: 'p4-state' }), { code: 'login-state-unknown' }); }); test('upstream OAuth error body never crosses bus response', async () => { const s = await setup(); await s.call('proxy.login.start', { provider: 'claude' }); s.client.oauthStatus = async () => ({ status: 'error', error: 'p4-private-code', state: 'p4-state' }); assert.deepEqual(await s.call('proxy.login.status', { state: 'p4-state' }), { status: 'error', reason: 'login-failed' }); }); for (const args of [{ accountId: 'bad', priority: 1 }, { accountId: 'a'.repeat(12), priority: 1.5 }, { accountId: 'a'.repeat(12), priority: 1001 }]) test('priority-invalid ' + JSON.stringify(args), async () => { const s = await setup(); await assert.rejects(s.call('proxy.priority.set', args), { code: 'priority-invalid' }); }); test('successful account configuration and login invalidate the catalogue', async () => { const { ProxyPage } = await import(moduleURL); let changed = 0; const client = { getCredentials: async () => ({ files: [] }), getUpstreamGroups: async () => [], putUpstreamGroups: async () => ({}), oauthStart: async () => ({ state: 'state', url: 'https://claude.ai/oauth/authorize?state=state' }), oauthStatus: async () => ({ status: 'ok' }) }; const page = new ProxyPage({ client, instanceTag: 'fixture', onChange: () => { changed++; } }); const scope = { business: 'fixture', human: 'owner' }; await page.request('proxy.apikey.add', { provider: 'z.ai', key: 'fixture-key' }, scope); assert.equal(changed, 2); await page.request('proxy.login.start', { provider: 'claude' }, scope); await page.request('proxy.login.status', { state: 'state' }, scope); assert.equal(changed, 3); });