Files
anonymous_github/test/review-completion-http.test.js
T

349 lines
12 KiB
JavaScript

require("ts-node/register/transpile-only");
const { expect } = require("chai");
const express = require("express");
const http = require("http");
const { createHash } = require("crypto");
const { setTimeout: delay } = require("timers/promises");
const {
createReviewCompletionService,
} = require("../src/server/service/review-completion-http");
const {
ReviewConsentError,
} = require("../src/server/service/review-owner-consent");
const client = "1".repeat(32),
intent = "2".repeat(32),
token = "3".repeat(64);
const exchangePath = "/service/v1/completions/exchange";
const readPath = `/service/v1/intents/${intent}/receipt`;
const command = () => ({
contract: "4open.artifacts/1",
clientId: client,
intentId: intent,
code: "4".repeat(64),
requestId: "5".repeat(32),
});
const receipt = () => ({
contract: "4open.artifacts/1",
clientId: client,
intentId: intent,
bindingId: "6".repeat(32),
submissionRef: "7".repeat(32),
entitlementId: "8".repeat(32),
policy: {
version: 1,
access: "restricted-review",
retainUntil: "2199-01-01T00:00:00Z",
},
});
const config = (scopes = ["completion.exchange", "receipt.read"]) =>
JSON.stringify({
version: 1,
keys: [
{
clientId: client,
keyId: "current",
tokenSHA256: createHash("sha256").update(token).digest("hex"),
notBefore: "2026-01-01T00:00:00Z",
notAfter: "2027-01-01T00:00:00Z",
scopes,
},
],
});
describe("review completion service HTTP transport", function () {
this.timeout(20000);
let server, backend, calls, clock;
async function start(raw = config(), beforeParser = true) {
const app = express();
app.set("trust proxy", "loopback");
if (!beforeParser) app.use(express.json());
app.use(
createReviewCompletionService(
raw === null ? undefined : raw,
backend,
() => clock,
),
);
server = await new Promise((resolve) => {
const s = app.listen(0, "127.0.0.1", () => resolve(s));
});
}
beforeEach(async () => {
clock = Date.parse("2026-09-27T12:00:00Z");
calls = [];
backend = {
exchange: async (id, body) => {
calls.push({ id, body });
return receipt();
},
receipt: async (id) => {
calls.push({ id });
return receipt();
},
};
});
afterEach(async () => {
if (server) {
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
server = undefined;
}
});
function request(path = exchangePath, options = {}) {
const body =
options.raw === undefined
? path === exchangePath && options.body !== false
? JSON.stringify(options.body || command())
: undefined
: options.raw;
return new Promise((resolve, reject) => {
const req = http.request(
{
hostname: "127.0.0.1",
port: server.address().port,
path,
method: options.method || (body === undefined ? "GET" : "POST"),
headers: {
"X-Forwarded-Proto": "https",
"X-4open-Artifact-Client-ID": client,
"X-4open-Artifact-Service-Key-ID": "current",
Authorization: "Bearer " + token,
...(body === undefined
? {}
: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body),
}),
...options.headers,
},
},
(res) => {
let text = "";
res.on("data", (chunk) => {
text += chunk;
});
res.on("end", () =>
resolve({
status: res.statusCode,
headers: res.headers,
body: JSON.parse(text),
}),
);
},
);
req.on("error", reject);
if (body !== undefined) req.write(body);
req.end();
});
}
it("requires scoped service credentials and returns the exact client receipt", async () => {
await start();
const result = await request();
expect(result.status).equal(200);
expect(result.body).deep.equal(receipt());
expect(result.headers["cache-control"]).equal("no-store");
expect(result.headers["referrer-policy"]).equal("no-referrer");
expect(result.headers["set-cookie"]).equal(undefined);
expect(result.headers["access-control-allow-origin"]).equal(undefined);
expect((await request(readPath)).status).equal(200);
expect(calls).length(2);
});
it("stays disabled without a dedicated registry", async () => {
await start(null);
expect((await request()).status).equal(404);
expect(calls).length(0);
});
it("does not permit a read credential to exchange a completion", async () => {
await start(config(["receipt.read"]));
expect((await request()).status).equal(401);
expect((await request(readPath)).status).equal(200);
expect(calls).length(1);
});
it("rejects capabilities-only configuration and malformed or duplicate registry fields", () => {
const old = JSON.parse(config());
delete old.keys[0].scopes;
for (const raw of [
JSON.stringify(old),
config().replace('"version":1', '"version":1,"version":1'),
config().replace('"receipt.read"', '"unknown"'),
"{",
])
expect(() => createReviewCompletionService(raw, backend)).to.throw(
"Invalid review exchange credentials",
);
});
it("rejects browser context, cleartext, encodings and duplicate authentication headers", async () => {
await start();
for (const headers of [
{ Cookie: "session=untrusted" },
{ Origin: "https://example.test" },
{ Referer: "https://example.test" },
{ "Sec-Fetch-Site": "none" },
{ "X-Forwarded-Proto": "http" },
{ "Content-Encoding": "gzip" },
{ Authorization: ["Bearer " + token, "Bearer " + token] },
]) {
const result = await request(exchangePath, { headers });
expect(result.status).oneOf([400, 401, 403]);
expect(result.headers.connection).equal("close");
}
expect(calls).length(0);
});
it("rejects noncanonical routes, queries, read bodies and wrong methods", async () => {
await start();
for (const path of [
exchangePath + "/",
exchangePath + "?x=1",
readPath + "?x=1",
"/service/v1/intents/" + intent.toUpperCase() + "F/receipt",
])
expect((await request(path, { method: "POST", raw: "{}" })).status).equal(
404,
);
expect(
(await request(exchangePath, { method: "GET", body: false })).status,
).equal(405);
expect(
(await request(readPath, { method: "GET", raw: "{}" })).status,
).equal(400);
expect(calls).length(0);
});
it("rejects duplicate decoded JSON keys, unknown fields, malformed UTF-8 and excessive bodies", async () => {
await start();
const valid = JSON.stringify(command());
for (const raw of [
valid.replace('"code":', '"code":"' + "0".repeat(64) + '","co\\u0064e":'),
JSON.stringify({ ...command(), extra: true }),
valid + "{}",
"[]",
Buffer.from([123, 34, 120, 34, 58, 34, 255, 34, 125]),
" ".repeat(65537),
])
expect((await request(exchangePath, { raw })).status).equal(400);
expect(calls).length(0);
});
it("rejects wrong client scope and unsupported wire versions", async () => {
await start();
expect(
(
await request(exchangePath, {
body: { ...command(), clientId: "a".repeat(32) },
})
).status,
).equal(403);
expect(
(
await request(exchangePath, {
body: { ...command(), contract: "4open.artifacts/2" },
})
).status,
).equal(422);
expect(calls).length(0);
});
it("fails closed if mounted after a JSON parser", async () => {
await start(config(), false);
expect((await request()).status).equal(503);
expect(calls).length(0);
});
it("rechecks credential expiry after backend work and filters invalid receipts", async () => {
backend.exchange = async () => {
clock = Date.parse("2027-01-01T00:00:00Z");
return receipt();
};
await start();
expect((await request()).status).equal(401);
clock = Date.parse("2026-09-27T12:00:00Z");
backend.exchange = async () => ({ ...receipt(), owner: "private-owner" });
const result = await request();
expect(result.status).equal(503);
expect(JSON.stringify(result.body)).not.include("private-owner");
});
it("uses fixed errors without exposing backend details", async () => {
await start();
for (const [kind, status] of [
["invalid", 400],
["forbidden", 403],
["expired", 410],
["conflict", 409],
["unavailable", 503],
]) {
backend.exchange = async () => {
throw new ReviewConsentError(kind);
};
const result = await request();
expect(result.status).equal(status);
expect(result.body.requestId).match(/^[a-f0-9]{32}$/);
}
backend.exchange = async () => {
throw new Error("secret database diagnostics");
};
const result = await request();
expect(result.status).equal(503);
expect(JSON.stringify(result.body)).not.include("diagnostics");
});
it("limits concurrent operations until the backend settles", async () => {
const pending = [];
backend.exchange = () => new Promise((resolve) => pending.push(resolve));
await start();
const requests = Array.from({ length: 4 }, () => request());
for (let i = 0; i < 100 && pending.length < 4; i++) await delay(5);
expect(pending).length(4);
expect((await request()).status).equal(503);
pending.forEach((resolve) => resolve(receipt()));
expect((await Promise.all(requests)).every((r) => r.status === 200)).equal(
true,
);
backend.exchange = async () => receipt();
expect((await request()).status).equal(200);
});
it("limits client requests and restores capacity in the next minute", async () => {
await start();
for (let i = 0; i < 60; i++)
expect((await request(readPath)).status).equal(200);
expect((await request(readPath)).status).equal(429);
clock += 60000;
expect((await request(readPath)).status).equal(200);
});
it("closes unauthorized incomplete uploads before reading their bodies", async () => {
await start();
const startTime = Date.now();
const result = await request(exchangePath, {
raw: "{",
headers: {
Authorization: "Bearer " + "0".repeat(64),
"Content-Length": "60000",
},
});
expect(result.status).equal(401);
expect(Date.now() - startTime).lessThan(2000);
expect(calls).length(0);
});
it("bounds authenticated incomplete uploads without invoking the backend", async () => {
await start();
const result = await request(exchangePath, {
raw: "{",
headers: { "Content-Length": "60000" },
});
expect(result.status).equal(503);
expect(calls).length(0);
});
it('supports overlapping rotation keys with independent validity windows', async () => {
const settings=JSON.parse(config()), nextToken='b'.repeat(64);
settings.keys[0].notAfter='2026-09-27T12:00:01Z';
settings.keys.push({...settings.keys[0],keyId:'next',tokenSHA256:createHash('sha256').update(nextToken).digest('hex'),notBefore:'2026-09-27T12:00:00Z',notAfter:'2027-01-01T00:00:00Z'});
await start(JSON.stringify(settings));
const headers={'X-4open-Artifact-Service-Key-ID':'next',Authorization:'Bearer '+nextToken};
expect((await request(readPath)).status).equal(200);
expect((await request(readPath,{headers})).status).equal(200);
clock+=1000; expect((await request(readPath)).status).equal(401);
expect((await request(readPath,{headers})).status).equal(200);
clock-=2000; expect((await request(readPath,{headers})).status).equal(401);
});
it('measures 50 authenticated receipt reads with a synthetic backend', async () => {
await start(); const times=[]; const process=require('process');
for(let i=0;i<50;i++) { const startTime=process.hrtime.bigint(); expect((await request(readPath)).status).equal(200); times.push(Number(process.hrtime.bigint()-startTime)/1e6); }
times.sort((a,b)=>a-b);
if(process.env.TEST_REVIEW_COMPLETION_HTTP_PERF) require('fs').writeFileSync(process.env.TEST_REVIEW_COMPLETION_HTTP_PERF,JSON.stringify({calls:50,workload:'loopback HTTP with trusted-proxy TLS metadata and synthetic backend, no provider or database',medianMs:times[24],p95Ms:times[47]},null,2)+'\n');
});
});