Add node registration claim transport
This commit is contained in:
parent
b8680e82e1
commit
27cd1645da
8 changed files with 369 additions and 25 deletions
|
|
@ -0,0 +1,153 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { generateKeyPairSync } from 'node:crypto';
|
||||
import { HttpNodeRegistrationClaimSource } from './node-registration-client.js';
|
||||
import {
|
||||
parseNodeRegistrationEndpointRegistry,
|
||||
resolveNodeRegistrationEndpoint,
|
||||
} from './node-registration.js';
|
||||
|
||||
const NOW = 1_786_291_200_000;
|
||||
const COMMIT = 'a'.repeat(40);
|
||||
const { publicKey } = generateKeyPairSync('ed25519');
|
||||
|
||||
function registeredEndpoint() {
|
||||
const registry = parseNodeRegistrationEndpointRegistry({
|
||||
endpoints: [{
|
||||
algorithm: 'Ed25519',
|
||||
domainIds: ['DOM-FIFTH-0001'],
|
||||
endpointId: 'GH-NODE-REG-001',
|
||||
nodeTypes: ['local-terminal'],
|
||||
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(),
|
||||
signerId: 'GH-NODE-REG-SIGNER-001',
|
||||
status: 'ACTIVE',
|
||||
url: 'https://guanghulab.com/api/ai/v1/node-registrations/claims',
|
||||
}],
|
||||
registryId: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001',
|
||||
schema: 'gh-aios.node-registration-endpoints/v1',
|
||||
state: 'CURRENT',
|
||||
version: '1.0.0',
|
||||
}, {
|
||||
repositoryId: 'REPO-012',
|
||||
sourceCommit: COMMIT,
|
||||
sourceUrl: `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/node-registration-endpoints.json`,
|
||||
});
|
||||
const endpoint = resolveNodeRegistrationEndpoint(registry, {
|
||||
domainId: 'DOM-FIFTH-0001',
|
||||
nodeType: 'local-terminal',
|
||||
});
|
||||
assert.ok(endpoint);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
function lookup(endpoint = registeredEndpoint()) {
|
||||
return {
|
||||
accountId: 'bingshuo',
|
||||
domainId: 'DOM-FIFTH-0001',
|
||||
endpoint,
|
||||
nodeId: 'LOCAL-001',
|
||||
nodeType: 'local-terminal' as const,
|
||||
};
|
||||
}
|
||||
|
||||
function responseBody(status: 'ISSUED' | 'NOT_FOUND' = 'ISSUED', overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
claim: status === 'ISSUED' ? { signed: 'claim' } : null,
|
||||
requestId: 'NODE-REQ-001',
|
||||
schema: 'gh-aios.node-registration-claim-response/v1',
|
||||
status,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function jsonResponse(body: unknown, init: ResponseInit = {}) {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status: 200,
|
||||
...init,
|
||||
headers: { 'Content-Type': 'application/json', ...init.headers },
|
||||
});
|
||||
}
|
||||
|
||||
test('posts an exact credentialless claim lookup only to the registered endpoint', async () => {
|
||||
let observedUrl = '';
|
||||
let observedInit: RequestInit | undefined;
|
||||
const source = new HttpNodeRegistrationClaimSource(async (url, init) => {
|
||||
observedUrl = url;
|
||||
observedInit = init;
|
||||
return jsonResponse(responseBody());
|
||||
}, () => NOW, () => 'NODE-REQ-001');
|
||||
|
||||
const claim = await source.read(lookup());
|
||||
|
||||
assert.deepEqual(claim, { signed: 'claim' });
|
||||
assert.equal(observedUrl, 'https://guanghulab.com/api/ai/v1/node-registrations/claims');
|
||||
assert.equal(observedInit?.method, 'POST');
|
||||
assert.equal(observedInit?.credentials, 'omit');
|
||||
assert.equal(observedInit?.redirect, 'manual');
|
||||
assert.equal(observedInit?.cache, 'no-store');
|
||||
assert.equal(observedInit?.referrerPolicy, 'no-referrer');
|
||||
assert.deepEqual(JSON.parse(String(observedInit?.body)), {
|
||||
accountId: 'bingshuo',
|
||||
domainId: 'DOM-FIFTH-0001',
|
||||
endpointId: 'GH-NODE-REG-001',
|
||||
issuedAt: NOW,
|
||||
nodeId: 'LOCAL-001',
|
||||
nodeType: 'local-terminal',
|
||||
requestId: 'NODE-REQ-001',
|
||||
schema: 'gh-aios.node-registration-claim-request/v1',
|
||||
});
|
||||
const headers = observedInit?.headers as Record<string, string>;
|
||||
assert.equal(headers.Authorization, undefined);
|
||||
assert.equal(headers.Cookie, undefined);
|
||||
});
|
||||
|
||||
test('returns null only for an exact NOT_FOUND response', async () => {
|
||||
const source = new HttpNodeRegistrationClaimSource(
|
||||
async () => jsonResponse(responseBody('NOT_FOUND')),
|
||||
() => NOW,
|
||||
() => 'NODE-REQ-001',
|
||||
);
|
||||
assert.equal(await source.read(lookup()), null);
|
||||
});
|
||||
|
||||
test('rejects caller-created endpoints before any network request', async () => {
|
||||
let called = false;
|
||||
const source = new HttpNodeRegistrationClaimSource(async () => {
|
||||
called = true;
|
||||
return jsonResponse(responseBody());
|
||||
}, () => NOW, () => 'NODE-REQ-001');
|
||||
const forged = { ...registeredEndpoint(), url: 'https://guanghulab.com/api/ai/v1/other' };
|
||||
|
||||
await assert.rejects(source.read(lookup(forged)), /transport_invalid/);
|
||||
assert.equal(called, false);
|
||||
});
|
||||
|
||||
test('rejects redirects, non-json responses, oversized bodies and mismatched envelopes', async () => {
|
||||
const cases: Array<() => Response> = [
|
||||
() => new Response(null, { status: 302, headers: { Location: 'https://guanghulab.com/api/ai/v1/other' } }),
|
||||
() => new Response('{}', { status: 200, headers: { 'Content-Type': 'text/plain' } }),
|
||||
() => new Response('x', { status: 200, headers: { 'Content-Type': 'application/json', 'Content-Length': '65537' } }),
|
||||
() => new Response(`"${'x'.repeat(65_536)}"`, { status: 200, headers: { 'Content-Type': 'application/json' } }),
|
||||
() => jsonResponse(responseBody('ISSUED', { requestId: 'NODE-REQ-OTHER' })),
|
||||
() => jsonResponse({ ...responseBody(), extra: true }),
|
||||
() => jsonResponse(responseBody('NOT_FOUND', { claim: {} })),
|
||||
];
|
||||
for (const makeResponse of cases) {
|
||||
const source = new HttpNodeRegistrationClaimSource(
|
||||
async () => makeResponse(),
|
||||
() => NOW,
|
||||
() => 'NODE-REQ-001',
|
||||
);
|
||||
await assert.rejects(source.read(lookup()), /transport_invalid/);
|
||||
}
|
||||
});
|
||||
|
||||
test('reduces network errors to one non-sensitive transport failure', async () => {
|
||||
const source = new HttpNodeRegistrationClaimSource(async () => {
|
||||
throw new Error('secret endpoint detail');
|
||||
}, () => NOW, () => 'NODE-REQ-001');
|
||||
await assert.rejects(source.read(lookup()), error => {
|
||||
assert.equal((error as Error).message, 'node_registration_claim_transport_invalid');
|
||||
return true;
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue