test(proxy): prepare empty-provider 404 regressions before normalization
This commit is contained in:
1 parent
c93df1608a
commit
e7438743ff
7 files changed
+180
-1
No files matched your search
@@ -491,3 +491,35 @@ silently turn unknown failures into empty data. Run lists/CLEAR precede tests.
|
||||
|
||||
No product or test changed in this receipt/proposal commit. Proposed patch is
|
||||
review material, not a fix claim or a substitute for RED/GREEN/upgrade evidence.
|
||||
|
||||
## Regression preparation — sage-4460, no Node execution
|
||||
|
||||
2026-10-25T07:38Z. Authored11 cases in two new whole test files:
|
||||
packages/model-proxy/tests/empty-provider.test.mjs (8): exact fixed GET404 ->[],
|
||||
actual404 receipt/discarded canary body/one request; credentials404 and key-list404
|
||||
remain proxy-client-refused; provider401/403/500 remain proxy-client-refused;
|
||||
providerPUT404 remains proxy-client-refused (exercises GET-only guard);200 array
|
||||
passes through. Every case pins exactly one call and status receipt, no retry,
|
||||
no response-body/management-key leakage into receipts or error output.
|
||||
packages/bus/tests/proxy-page-empty-provider.test.mjs (3): fresh404 empty account
|
||||
list followed by one first-key PUT and a public safe account; malformed200 null
|
||||
and object each refuse existing proxy-down with no write/retry.
|
||||
|
||||
Stand-in addition only: optional emptyProviderResponse controls the empty-route
|
||||
status/body for these new cases. Existing fake baseline and every old assertion
|
||||
and timeout remain unchanged. Imports are declared directly to ManagementClient
|
||||
and the existing management fixture (which imports ProxyPage/client). Static
|
||||
single-line import declarations are recorded in empty-provider-prepared-imports
|
||||
and rooted in the prior esbuild graph; no fixture-text search selects tests.
|
||||
Exact initial RED list: empty-provider-red-run-list.txt. Prepared source/test/patch
|
||||
hashes: empty-provider-prepared-MANIFEST.sha256. Product files remain unmodified;
|
||||
proposed-empty-provider.patch is still NOT APPLIED.
|
||||
|
||||
Expected first RED: new exact404 client case rejects proxy-client-refused instead
|
||||
of returning[]; new fresh account case rejects proxy-down before add. No new
|
||||
refusal code; all negative cases preserve existing named refusals. Observe this
|
||||
RED under the next CLEAR before applying the approved scoped source patch.
|
||||
Required later GREEN should include both new whole files and the8 focused whole
|
||||
files because the helper is shared; full gate and P2 key-join still wait their
|
||||
separate authorized stage. No Node syntax check, test, acceptance, mutant or
|
||||
proxy process ran during preparation. Slot remains with Magica331r4.
|
||||
@@ -0,0 +1,8 @@
|
||||
ed5dfcda6db77921f90f1eb587fb0044d6472fb613ecaa98445ed42bc04d5d46 packages/model-proxy/tests/empty-provider.test.mjs
|
||||
c55cc0bb47cd7716a2c31c665cd2dd426e2c54ab7b629637bdc12089dfeb40ce packages/bus/tests/proxy-page-empty-provider.test.mjs
|
||||
01c18d8d09dac72bed45ce302a88cea449ed22ffde374529cf784bad38adbd72 packages/bus/tests/proxy-page-fixture.mjs
|
||||
aa73c4cd454742f7745900e8481f7425341ed7d236b104589790ed254c8bd69f packages/model-proxy/src/client.mjs
|
||||
14acdb4d3fdaabad508eec7378740b13c175d4bdd0c6e707a673138eff82365b packages/bus/src/proxy-page.mjs
|
||||
b19292cad28bd19427fac932c6150429968e184f4a6f000d948919e76908eb05 agents/poe/work/queue-330/proposed-empty-provider.patch
|
||||
ea7584f8935251baa9c32bf675343e8c8cbe19a45c4e7dc48675e14b4409bb1c agents/poe/work/queue-330/empty-provider-prepared-imports.json
|
||||
5d227ae19d600c2fb0e375673045705c4f5bcc00311493a577108caddb38704a agents/poe/work/queue-330/empty-provider-red-run-list.txt
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"packages/model-proxy/tests/empty-provider.test.mjs": {
|
||||
"staticImports": [
|
||||
"node:test",
|
||||
"node:assert/strict",
|
||||
"node:http",
|
||||
"packages/model-proxy/src/client.mjs"
|
||||
],
|
||||
"existingParsedClosureRoots": [
|
||||
"packages/model-proxy/src/client.mjs"
|
||||
]
|
||||
},
|
||||
"packages/bus/tests/proxy-page-empty-provider.test.mjs": {
|
||||
"staticImports": [
|
||||
"node:test",
|
||||
"node:assert/strict",
|
||||
"packages/bus/tests/proxy-page-fixture.mjs"
|
||||
],
|
||||
"existingParsedClosureRoots": [
|
||||
"packages/bus/tests/proxy-page-fixture.mjs"
|
||||
]
|
||||
},
|
||||
"packages/bus/tests/proxy-page-fixture.mjs": {
|
||||
"staticImports": [
|
||||
"node:http",
|
||||
"packages/model-proxy/src/client.mjs",
|
||||
"packages/bus/src/proxy-page.mjs"
|
||||
],
|
||||
"existingParsedClosureRoots": [
|
||||
"packages/model-proxy/src/client.mjs",
|
||||
"packages/bus/src/proxy-page.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
packages/model-proxy/tests/empty-provider.test.mjs
|
||||
packages/bus/tests/proxy-page-empty-provider.test.mjs
|
||||
@@ -0,0 +1,33 @@
|
||||
// P4 real client/page regression: fresh pinned proxy404 must allow the first key.
|
||||
// Malformed successful bodies still fail closed with the existing refusal code.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { fakeManagement } from './proxy-page-fixture.mjs';
|
||||
const route = '/v8/management/config/api-keys/claude';
|
||||
test('fresh404 yields no accounts, then adds the first key once with no value leakage', async t => {
|
||||
const canary = 'p4-first-key';
|
||||
const s = await fakeManagement(t, { emptyProviderResponse: { status: 404, body: { error: 'p4-body-canary' } } });
|
||||
const empty = await s.call('proxy.accounts');
|
||||
assert.equal(Array.isArray(empty.accounts), true); assert.equal(empty.accounts.length, 0);
|
||||
const added = await s.call('proxy.apikey.add', { provider: 'z.ai', key: canary });
|
||||
assert.equal(added.added, true);
|
||||
const populated = await s.call('proxy.accounts');
|
||||
assert.equal(populated.accounts.length, 1); assert.equal(populated.accounts[0].provider, 'z.ai');
|
||||
assert.equal(s.state.receives, 1, 'one PUT, no retry');
|
||||
assert.equal(s.state.groups[0].keys[0]['api-key'] === canary, true);
|
||||
assert.deepEqual(s.state.calls, ['GET /v8/management/credentials', `GET ${route}`, `GET ${route}`, `PUT ${route}`,
|
||||
'GET /v8/management/credentials', `GET ${route}`]);
|
||||
assert.deepEqual(s.state.receipts.filter(r => r.call === `GET ${route}`).map(r => r.status), [404, 404, 200]);
|
||||
for (const value of [canary, 'p4-body-canary', 'p4-management']) {
|
||||
assert.equal(JSON.stringify([empty, added, populated]).includes(value), false);
|
||||
assert.equal(JSON.stringify(s.state.receipts).includes(value), false);
|
||||
}
|
||||
});
|
||||
for (const [name, body] of [['null', null], ['object', { groups: [] }]])
|
||||
test(`malformed200 ${name} remains proxy-down, no retry or write`, async t => {
|
||||
const s = await fakeManagement(t, { emptyProviderResponse: { status: 200, body } });
|
||||
await assert.rejects(s.call('proxy.accounts'), { code: 'proxy-down' });
|
||||
assert.deepEqual(s.state.calls, ['GET /v8/management/credentials', `GET ${route}`]);
|
||||
assert.equal(s.state.receives, 0);
|
||||
assert.deepEqual(s.state.receipts.map(r => r.status), [200, 200]);
|
||||
});
|
||||
@@ -16,7 +16,11 @@ export async function fakeManagement(t, options = {}) {
|
||||
if (req.headers.authorization !== 'Bearer p4-management') return reply(401, { error: 'refused' });
|
||||
if (u.pathname === '/v8/management/credentials') return reply(200, { files: state.files, observed_at: '2026-10-24T00:00:00Z' });
|
||||
if (u.pathname === '/v8/management/config/api-keys/claude') {
|
||||
if (req.method === 'GET') return reply(200, state.groups);
|
||||
if (req.method === 'GET') {
|
||||
if (state.groups.length === 0 && state.emptyProviderResponse)
|
||||
return reply(state.emptyProviderResponse.status, state.emptyProviderResponse.body);
|
||||
return reply(200, state.groups);
|
||||
}
|
||||
// Stand-in treats auth_index as derived/read-only metadata.
|
||||
if (value.some(g => (g.keys ?? []).some(k => Object.hasOwn(k, 'auth_index')))) return reply(422, { error: 'derived field' });
|
||||
state.receives++;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// P4 pinned8.0.42 regression: absent Claude provider config is HTTP404.
|
||||
// Normalize only that fixed GET; all other methods/endpoints/statuses refuse.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import http from 'node:http';
|
||||
import { ManagementClient } from '../src/client.mjs';
|
||||
const endpoint = '/v8/management/config/api-keys/claude';
|
||||
const bodyCanary = 'p4-body-canary';
|
||||
async function fixture(t, status, body = { error: bodyCanary, 'api-key': bodyCanary }) {
|
||||
const requests = [], receipts = [];
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const chunks = []; for await (const chunk of req) chunks.push(chunk);
|
||||
requests.push({ method: req.method, path: req.url, body: Buffer.concat(chunks).toString('utf8') });
|
||||
res.writeHead(status, { 'content-type': 'application/json' }); res.end(JSON.stringify(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, requests, receipts };
|
||||
}
|
||||
function evidence(s, method, path, status) {
|
||||
assert.equal(s.requests.length, 1, 'one call, no retry');
|
||||
assert.deepEqual(s.requests.map(r => [r.method, r.path]), [[method, path]]);
|
||||
assert.deepEqual(s.receipts, [{ call: `${method} ${path}`, status, keyIds: [] }]);
|
||||
assert.equal(JSON.stringify(s.receipts).includes(bodyCanary), false);
|
||||
assert.equal(JSON.stringify(s.receipts).includes('p4-management'), false);
|
||||
}
|
||||
async function refused(operation) {
|
||||
let failure;
|
||||
try { await operation(); } catch (error) { failure = error; }
|
||||
assert.equal(failure?.code, 'proxy-client-refused');
|
||||
assert.equal(failure.message.includes(bodyCanary), false, 'no error-body value escapes');
|
||||
assert.equal(JSON.stringify(failure).includes(bodyCanary), false);
|
||||
}
|
||||
test('empty provider GET404 yields empty array, discards body and receipts actual404 once', async t => {
|
||||
const s = await fixture(t, 404);
|
||||
const result = await s.client.getUpstreamGroups();
|
||||
assert.equal(Array.isArray(result), true); assert.equal(result.length, 0);
|
||||
assert.equal(JSON.stringify(result).includes(bodyCanary), false);
|
||||
evidence(s, 'GET', endpoint, 404);
|
||||
});
|
||||
for (const status of [401, 403, 500]) test(`provider GET${status} remains proxy-client-refused, no retry`, async t => {
|
||||
const s = await fixture(t, status);
|
||||
await refused(() => s.client.getUpstreamGroups());
|
||||
evidence(s, 'GET', endpoint, status);
|
||||
});
|
||||
for (const [method, path] of [['getCredentials', '/v8/management/credentials'], ['getKeys', '/v8/management/config/access/api-keys']])
|
||||
test(`${method}404 remains proxy-client-refused, no retry`, async t => {
|
||||
const s = await fixture(t, 404);
|
||||
await refused(() => s.client[method]());
|
||||
evidence(s, 'GET', path, 404);
|
||||
});
|
||||
test('provider PUT404 remains proxy-client-refused, never treated as empty GET', async t => {
|
||||
const s = await fixture(t, 404);
|
||||
await refused(() => s.client.putUpstreamGroups([{ name: 'fixture', keys: [{ 'api-key': 'p4-first-key' }] }]));
|
||||
evidence(s, 'PUT', endpoint, 404);
|
||||
assert.equal(s.requests[0].body.includes('p4-first-key'), true);
|
||||
assert.equal(JSON.stringify(s.receipts).includes('p4-first-key'), false);
|
||||
});
|
||||
test('provider GET200 array passes through, with receipt and no retry', async t => {
|
||||
const s = await fixture(t, 200, [{ name: 'fixture', keys: [] }]);
|
||||
const result = await s.client.getUpstreamGroups();
|
||||
assert.equal(Array.isArray(result), true); assert.equal(result.length, 1);
|
||||
assert.equal(result[0].name === 'fixture' && Array.isArray(result[0].keys), true);
|
||||
evidence(s, 'GET', endpoint, 200);
|
||||
});
|
||||
Reference in new issue
Block a user