57 lines
3.5 KiB
JavaScript
57 lines
3.5 KiB
JavaScript
// P4 client contract; endpoint/payload cases are probe-pending on PIN8.0.42.
|
|
// Catches management key/body/query values leaking into receipts, redirects/retries.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import http from 'node:http';
|
|
import { ManagementClient } from '../src/client.mjs';
|
|
async function setup(t, handler) {
|
|
const calls = [], receipts = [];
|
|
const server = http.createServer(async (req, res) => {
|
|
let body = ''; for await (const c of req) body += c;
|
|
calls.push({ method: req.method, path: req.url, auth: req.headers.authorization, body });
|
|
const answer = handler?.(req, body) ?? { status: 200, body: { status: 'ok' } };
|
|
res.writeHead(answer.status, { 'content-type': 'application/json', ...answer.headers });
|
|
res.end(JSON.stringify(answer.body));
|
|
});
|
|
await new Promise(r => server.listen(0, '127.0.0.1', r));
|
|
t.after(() => new Promise(r => server.close(r)));
|
|
const client = new ManagementClient({ baseUrl: `http://127.0.0.1:${server.address().port}`, key: 'p4-management', receipt: r => receipts.push(r) });
|
|
return { client, calls, receipts };
|
|
}
|
|
test('OAuth management calls keep URL and state out of receipts', async t => {
|
|
const s = await setup(t);
|
|
assert.equal(typeof s.client.oauthStart, 'function');
|
|
await s.client.oauthStart('claude');
|
|
await s.client.oauthCallback('http://localhost/callback?state=p4-state&code=p4-code');
|
|
await s.client.oauthStatus('p4-state');
|
|
await s.client.oauthCancel('p4-state');
|
|
assert.deepEqual(s.calls.map(({ method, path }) => [method, path]), [
|
|
['GET', '/v8/management/oauth/auth-url?provider=claude'], ['POST', '/v8/management/oauth/callback'],
|
|
['GET', '/v8/management/oauth/status?state=p4-state'], ['DELETE', '/v8/management/oauth/session?state=p4-state'],
|
|
]);
|
|
assert.ok(s.calls.every(c => c.auth === 'Bearer p4-management'));
|
|
for (const value of ['p4-state', 'p4-code', 'localhost', 'p4-management']) assert.equal(JSON.stringify(s.receipts).includes(value), false);
|
|
});
|
|
test('upstream key goes only in request body and upstream refusal body is discarded', async t => {
|
|
const s = await setup(t, () => ({ status: 400, body: { error: 'p4-canary-key' } }));
|
|
assert.equal(typeof s.client.putUpstreamGroups, 'function');
|
|
await assert.rejects(s.client.putUpstreamGroups([{ name: 'z.ai', keys: [{ 'api-key': 'p4-canary-key' }] }]),
|
|
e => !e.message.includes('p4-canary-key'));
|
|
assert.equal(s.calls.length, 1);
|
|
assert.ok(s.calls[0].body.includes('p4-canary-key'));
|
|
assert.equal(JSON.stringify(s.receipts).includes('p4-canary-key'), false);
|
|
});
|
|
test('redirected management calls are not followed or retried', async t => {
|
|
const s = await setup(t, () => ({ status: 302, headers: { location: 'http://127.0.0.1:8317/' }, body: {} }));
|
|
assert.equal(typeof s.client.getCredentials, 'function');
|
|
await assert.rejects(s.client.getCredentials(), { code: 'proxy-client-redirect' });
|
|
assert.equal(s.calls.length, 1);
|
|
});
|
|
test('credential model read encodes the name and keeps it out of receipts', async t => {
|
|
const s = await setup(t, () => ({ status: 200, body: { models: [{ id: 'fixture-model' }] } }));
|
|
assert.deepEqual(await s.client.getCredentialModels('fixture name.json'), { models: [{ id: 'fixture-model' }] });
|
|
assert.equal(s.calls[0].path, '/v8/management/credentials/models?name=fixture%20name.json');
|
|
assert.equal(JSON.stringify(s.receipts).includes('fixture name.json'), false);
|
|
for (const name of ['', 'x\n', 'x'.repeat(513)]) await assert.rejects(async () => s.client.getCredentialModels(name), { code: 'proxy-client-invalid' });
|
|
});
|