From b776ff7e5abcc58d875d7d3b439edfde51a80c7f Mon Sep 17 00:00:00 2001 From: Thomas Durieux <5577568+tdurieux@users.noreply.github.com> Date: Sun, 27 Sep 2026 13:50:20 +0000 Subject: [PATCH] Add bounded authenticated review intent HTTPS consumer --- docs/review-intent-client.md | 20 + src/server/service/review-intent-client.ts | 412 +++++++++++++++++++++ test/fixtures/review-intent-client.js | 46 +++ test/review-intent-client.test.js | 361 ++++++++++++++++++ 4 files changed, 839 insertions(+) create mode 100644 docs/review-intent-client.md create mode 100644 src/server/service/review-intent-client.ts create mode 100644 test/fixtures/review-intent-client.js create mode 100644 test/review-intent-client.test.js diff --git a/docs/review-intent-client.md b/docs/review-intent-client.md new file mode 100644 index 0000000..8c2fcd6 --- /dev/null +++ b/docs/review-intent-client.md @@ -0,0 +1,20 @@ +# Review intent consumption client + +`src/server/service/review-intent-client.ts` consumes a saved review-side link intent over authenticated HTTPS. The caller supplies a trusted, exact HTTPS origin, a client-bound service credential with a validity window, and an explicit request ID. The returned policy and opaque identifiers come from the authenticated review response. Browser-supplied access, retention and callback fields are rejected. + +The factory returns only `consume`; it copies credential values into a closure. A call makes one POST to `/service/v1/artifact-intents/consume`, validates the peer certificate with Node's trust store, and sends no browser cookies or origin. Redirects and automatic retries are disabled. An uncertain result requires the caller to retain the same request ID for an explicit retry. + +Limits are four active attempts, a ten-second total deadline, five-second inactivity timeout, 16 KiB response headers and 64 KiB body. An AbortSignal cancels the attempt. Invalid credentials or payloads fail before I/O. Responses must match the configured client and requested intent, use the v1 closed schema and valid future timestamps, and omit cookies and content encoding. Duplicate JSON keys, including escaped spellings, invalid UTF-8 and unknown fields fail. Errors expose only a fixed category; remote bodies and credentials are not included. + +This module is not wired into a route or configured in production. It does not authenticate an artifact owner, obtain consent, create a binding, exchange a completion code, or establish browser callback authority. Those are separate pending tasks. Credential distribution and upstream deployment also remain pending. + +## Validation + +Local tests use a disposable certificate and loopback HTTPS server. A child process loads that certificate through `NODE_EXTRA_CA_CERTS` at startup; an independent child with ordinary trust rejects it before sending credentials. No TLS bypass is used. Tests cover successful policy reads, copied configuration, request-ID replay, rejection before I/O, status classification without retry, malformed and oversized responses, duplicate keys, four-slot saturation and recovery, cancellation, credential expiry, inactivity and slow-stream total deadlines. + +- `npx mocha test/review-intent-client.test.js`: 11 behavioral cases; one performance case is opt-in. +- `npm test`: 712 pass, 50 pending opt-in or environment-dependent cases. +- Full TypeScript check and targeted ESLint pass. +- Opt-in 100-request performance run: fresh loopback TLS connections, mean 12.36 ms, median 12 ms, p95 15 ms. These are local synthetic timings, not an external-service latency promise. + +Run the measured test with `TEST_REVIEW_INTENT_PERF=1 TEST_REVIEW_INTENT_PERF_REPORT=/tmp/review-intent-perf.json npx mocha test/review-intent-client.test.js`. diff --git a/src/server/service/review-intent-client.ts b/src/server/service/review-intent-client.ts new file mode 100644 index 0000000..4e918a2 --- /dev/null +++ b/src/server/service/review-intent-client.ts @@ -0,0 +1,412 @@ +import * as https from "https"; +import { ClientRequest } from "http"; + +const contract = "4open.artifacts/1", + limit = 65536; +const opaque = /^[a-f0-9]{32}$/, + secret = /^[a-f0-9]{64}$/; +export type IntentClientFailure = + | "invalid" + | "busy" + | "unavailable" + | "rejected" + | "conflict" + | "expired" + | "not-found" + | "protocol"; +export class IntentClientError extends Error { + constructor(public readonly kind: IntentClientFailure) { + super("Review intent consumption " + kind); + this.name = "IntentClientError"; + } +} +export interface ReviewIntentClientConfig { + origin: string; + clientId: string; + keyId: string; + token: string; + notBefore: string; + notAfter: string; +} +export interface IntentConsumption { + contract: string; + clientId: string; + intentId: string; + token: string; + requestId: string; +} +export type ConsumedReviewIntent = Readonly<{ + contract: string; + clientId: string; + intentId: string; + submissionRef: string; + callbackId: string; + entitlementId: string; + expiresAt: string; + policy: Readonly<{ + version: number; + access: "anonymous-link" | "restricted-review"; + retainUntil: string; + }>; +}>; +function fail(kind: IntentClientFailure): never { + throw new IntentClientError(kind); +} +function object(value: unknown, fields: string[]): Record { + if (!value || typeof value !== "object" || Array.isArray(value)) + fail("protocol"); + const out = value as Record; + if ( + Object.keys(out).length !== fields.length || + fields.some((k) => !Object.prototype.hasOwnProperty.call(out, k)) + ) + fail("protocol"); + return out; +} +function instant(value: unknown): number { + if ( + typeof value !== "string" || + !/^2[01][0-9]{2}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/.test(value) + ) + fail("protocol"); + const time = Date.parse(value); + if ( + !Number.isFinite(time) || + new Date(time).toISOString() !== value.replace("Z", ".000Z") + ) + fail("protocol"); + return time; +} +function id(value: unknown): value is string { + return typeof value === "string" && opaque.test(value); +} + +// JSON.parse checks syntax; this bounded walk additionally rejects duplicate +// decoded keys, including escaped spellings, before typed field validation. +function decode(bytes: Buffer): unknown { + if (bytes.length > limit) fail("protocol"); + const text = bytes.toString("utf8"); + if (!Buffer.from(text, "utf8").equals(bytes)) fail("protocol"); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + fail("protocol"); + } + let at = 0; + const space = () => { + while (/\s/.test(text[at] || "") && at < text.length) at++; + }; + const string = (): string => { + const start = at++; + while (at < text.length) { + if (text[at] === "\\") { + at += 2; + continue; + } + if (text[at++] === '"') return JSON.parse(text.slice(start, at)); + } + return fail("protocol"); + }; + const walk = (depth: number): void => { + if (depth > 8) fail("protocol"); + space(); + if (text[at] === '"') { + string(); + return; + } + if (text[at] === "{" || text[at] === "[") { + const isObject = text[at++] === "{", + end = isObject ? "}" : "]", + keys = new Set(); + space(); + if (text[at] === end) { + at++; + return; + } + for (;;) { + space(); + if (isObject) { + const key = string(); + if (keys.has(key)) fail("protocol"); + keys.add(key); + space(); + at++; // colon; syntax already checked + } + walk(depth + 1); + space(); + if (text[at++] === end) return; + } + } + while (at < text.length && !/[\s,}\]]/.test(text[at])) at++; + }; + walk(0); + space(); + if (at !== text.length) fail("protocol"); + return parsed; +} +function intent( + bytes: Buffer, + clientId: string, + intentId: string, +): ConsumedReviewIntent { + const p = object(decode(bytes), [ + "contract", + "clientId", + "intentId", + "submissionRef", + "callbackId", + "policy", + "expiresAt", + "entitlementId", + ]); + const policy = object(p.policy, ["version", "access", "retainUntil"]), + now = Date.now(); + if ( + p.contract !== contract || + p.clientId !== clientId || + p.intentId !== intentId || + !id(p.submissionRef) || + !id(p.callbackId) || + !id(p.entitlementId) + ) + fail("protocol"); + if ( + !Number.isSafeInteger(policy.version) || + (policy.version as number) < 1 || + (policy.access !== "anonymous-link" && + policy.access !== "restricted-review") || + instant(policy.retainUntil) <= now + ) + fail("protocol"); + const expires = instant(p.expiresAt); + if (expires <= now || expires > now + 600000) fail("protocol"); + return Object.freeze({ + contract, + clientId, + intentId, + submissionRef: p.submissionRef, + callbackId: p.callbackId, + entitlementId: p.entitlementId, + expiresAt: p.expiresAt as string, + policy: Object.freeze({ + version: policy.version as number, + access: policy.access as "anonymous-link" | "restricted-review", + retainUntil: policy.retainUntil as string, + }), + }); +} + +/** Trusted server configuration only. One attempt per call; the caller retains + * its request ID for an explicit retry. No browser callback or owner authority + * is established by consuming an intent. No credentials are exposed on the + * returned object, through serialization, or in transport errors. */ +export function createReviewIntentConsumer(config: ReviewIntentClientConfig) { + let origin: string, + clientId: string, + keyId: string, + token: string, + from: number, + until: number; + try { + const c = object(config, [ + "origin", + "clientId", + "keyId", + "token", + "notBefore", + "notAfter", + ]); + if ( + typeof c.origin !== "string" || + c.origin.length > 2048 || + !id(c.clientId) || + typeof c.keyId !== "string" || + !/^[A-Za-z0-9_-]{1,64}$/.test(c.keyId) || + typeof c.token !== "string" || + !secret.test(c.token) + ) + fail("invalid"); + const url = new URL(c.origin); + if ( + url.protocol !== "https:" || + url.origin !== c.origin || + url.username || + url.password + ) + fail("invalid"); + origin = c.origin; + clientId = c.clientId; + keyId = c.keyId; + token = c.token; + from = instant(c.notBefore); + until = instant(c.notAfter); + if (until <= from || Date.now() < from || Date.now() >= until) + fail("invalid"); + } catch { + return fail("invalid"); + } + let active = 0; + return Object.freeze({ + consume( + input: IntentConsumption, + signal?: AbortSignal, + ): Promise { + let body: Buffer, expectedIntent: string; + try { + const p = object(input, [ + "contract", + "clientId", + "intentId", + "token", + "requestId", + ]); + if ( + p.contract !== contract || + p.clientId !== clientId || + !id(p.intentId) || + !id(p.requestId) || + typeof p.token !== "string" || + !secret.test(p.token) || + Date.now() < from || + Date.now() >= until + ) + fail("invalid"); + expectedIntent = p.intentId; + body = Buffer.from( + JSON.stringify({ + contract, + clientId, + intentId: expectedIntent, + token: p.token, + requestId: p.requestId, + }), + ); + } catch { + return Promise.reject(new IntentClientError("invalid")); + } + if (signal?.aborted) + return Promise.reject(new IntentClientError("unavailable")); + if (active >= 4) return Promise.reject(new IntentClientError("busy")); + active++; + return new Promise((resolve, reject) => { + let request: ClientRequest | undefined, + settled = false; + const abort = () => finish("unavailable"); + const timer = setTimeout(abort, 10000); + function finish( + error?: IntentClientFailure, + value?: ConsumedReviewIntent, + ) { + if (settled) return; + settled = true; + clearTimeout(timer); + signal?.removeEventListener("abort", abort); + active--; + if (error) { + request?.destroy(); + reject(new IntentClientError(error)); + } else resolve(value!); + } + signal?.addEventListener("abort", abort, { once: true }); + try { + request = https.request( + origin + "/service/v1/artifact-intents/consume", + { + method: "POST", + agent: false, + rejectUnauthorized: true, + minVersion: "TLSv1.2", + maxHeaderSize: 16384, + headers: { + Authorization: "Bearer " + token, + "X-4open-Artifact-Client-Id": clientId, + "X-4open-Artifact-Service-Key-Id": keyId, + "Content-Type": "application/json", + Accept: "application/json", + "Accept-Encoding": "identity", + "Content-Length": body.length, + }, + }, + (response) => { + response.once("aborted", abort); + response.once("error", abort); + const status = response.statusCode || 0; + if (status !== 200) { + const kind: IntentClientFailure = + status === 404 + ? "not-found" + : status === 409 + ? "conflict" + : status === 410 + ? "expired" + : [400, 401, 403, 422].includes(status) + ? "rejected" + : [408, 429, 500, 502, 503, 504].includes(status) + ? "unavailable" + : "protocol"; + finish(kind); + return; + } + const types = response.rawHeaders.filter( + (_v, i) => + i % 2 === 0 && + response.rawHeaders[i].toLowerCase() === "content-type", + ).length; + const length = response.headers["content-length"]; + if ( + types !== 1 || + !/^application\/json(?:\s*;\s*charset=(?:utf-8|"utf-8"))?\s*$/i.test( + response.headers["content-type"] || "", + ) || + response.headers["content-encoding"] !== undefined || + response.headers["set-cookie"] !== undefined || + (length !== undefined && + (!/^[0-9]+$/.test(length) || Number(length) > limit)) + ) { + finish("protocol"); + return; + } + let size = 0; + const chunks: Buffer[] = []; + response.on("data", (chunk: Buffer) => { + size += chunk.length; + if (size > limit) finish("protocol"); + else if (!settled) chunks.push(chunk); + }); + response.once("end", () => { + if (settled) return; + if ( + signal?.aborted || + Date.now() < from || + Date.now() >= until + ) { + finish("unavailable"); + return; + } + try { + finish( + undefined, + intent( + Buffer.concat(chunks, size), + clientId, + expectedIntent, + ), + ); + } catch { + finish("protocol"); + } + }); + }, + ); + request.once("error", abort); + request.setTimeout(5000, abort); + if (signal?.aborted) abort(); + if (!settled) request.end(body); + } catch { + finish("unavailable"); + } + }); + }, + }); +} diff --git a/test/fixtures/review-intent-client.js b/test/fixtures/review-intent-client.js new file mode 100644 index 0000000..d15216b --- /dev/null +++ b/test/fixtures/review-intent-client.js @@ -0,0 +1,46 @@ +require("ts-node/register/transpile-only"); +const process = require("process"); +const { setTimeout } = require("timers"); +const { AbortController } = globalThis; +const { + createReviewIntentConsumer, +} = require("../../src/server/service/review-intent-client"); + +process.on("message", async ({ id, config, input, mode }) => { + try { + const client = createReviewIntentConsumer(config); + const controller = new AbortController(); + if (mode === "preabort") controller.abort(); + if (mode === "abort") setTimeout(() => controller.abort(), 100); + if (mode === "mutate") { + config.token = "invalid"; + config.origin = "https://invalid.example"; + } + if (mode === "parallel") { + const pending = Array.from({ length: 5 }, () => + client.consume(input, controller.signal), + ); + const results = await Promise.allSettled(pending); + const followup = await client.consume(input); + process.send({ + id, + results: results.map((r) => + r.status === "fulfilled" ? "ok" : r.reason.kind, + ), + followup, + }); + return; + } + const started = Date.now(); + const result = await client.consume(input, controller.signal); + process.send({ + id, + result, + elapsedMs: Date.now() - started, + frozen: Object.isFrozen(result) && Object.isFrozen(result.policy), + serialized: JSON.stringify(client), + }); + } catch (error) { + process.send({ id, kind: error.kind, message: error.message }); + } +}); diff --git a/test/review-intent-client.test.js b/test/review-intent-client.test.js new file mode 100644 index 0000000..d7a0571 --- /dev/null +++ b/test/review-intent-client.test.js @@ -0,0 +1,361 @@ +const { expect } = require("chai"); +const https = require("https"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const process = require("process"); +const { fork, execFileSync } = require("child_process"); +const { + setTimeout, + clearTimeout, + setInterval, + clearInterval, +} = require("timers"); +const clientId = "a".repeat(32), + intentId = "c".repeat(32); +const at = (offset) => + new Date(Math.floor((Date.now() + offset) / 1000) * 1000) + .toISOString() + .replace(".000Z", "Z"); +const input = () => ({ + contract: "4open.artifacts/1", + clientId, + intentId, + token: "d".repeat(64), + requestId: "e".repeat(32), +}); +const intent = () => ({ + contract: "4open.artifacts/1", + clientId, + intentId, + submissionRef: "f".repeat(32), + callbackId: "1".repeat(32), + entitlementId: "2".repeat(32), + expiresAt: at(120000), + policy: { + version: 1, + access: "restricted-review", + retainUntil: at(86400000), + }, +}); + +describe("review intent HTTPS client", function () { + this.timeout(20000); + let server, + child, + folder, + origin, + handler, + calls, + serial = 0; + const pending = new Map(); + const config = () => ({ + origin, + clientId, + keyId: "rotation-1", + token: "b".repeat(64), + notBefore: at(-60000), + notAfter: at(3600000), + }); + function call(options = {}) { + const id = ++serial; + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + pending.delete(id); + reject(new Error("child test timed out")); + }, 18000); + pending.set(id, (value) => { + clearTimeout(timer); + resolve(value); + }); + child.send({ id, config: config(), input: input(), ...options }); + }); + } + before(async function () { + folder = fs.mkdtempSync(path.join(os.tmpdir(), "review-intent-tls-")); + execFileSync( + "openssl", + [ + "req", + "-x509", + "-newkey", + "rsa:2048", + "-nodes", + "-days", + "1", + "-subj", + "/CN=localhost", + "-addext", + "subjectAltName=IP:127.0.0.1,DNS:localhost", + "-keyout", + path.join(folder, "key"), + "-out", + path.join(folder, "cert"), + ], + { stdio: "ignore" }, + ); + server = https.createServer( + { + key: fs.readFileSync(path.join(folder, "key")), + cert: fs.readFileSync(path.join(folder, "cert")), + }, + (req, res) => { + const chunks = []; + req.on("data", (chunk) => chunks.push(chunk)); + req.on("end", () => { + calls.push({ + method: req.method, + url: req.url, + headers: req.headers, + body: JSON.parse(Buffer.concat(chunks).toString()), + }); + handler(req, res); + }); + }, + ); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + origin = "https://127.0.0.1:" + server.address().port; + child = fork(path.join(__dirname, "fixtures/review-intent-client.js"), { + env: { ...process.env, NODE_EXTRA_CA_CERTS: path.join(folder, "cert") }, + stdio: ["ignore", "ignore", "pipe", "ipc"], + }); + child.on("message", (message) => { + const done = pending.get(message.id); + pending.delete(message.id); + if (done) done(message); + }); + }); + after(async function () { + child?.kill(); + server?.closeAllConnections(); + if (server) await new Promise((resolve) => server.close(resolve)); + if (folder) fs.rmSync(folder, { recursive: true, force: true }); + }); + beforeEach(() => { + calls = []; + handler = (_req, res) => { + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(intent())); + }; + }); + it("uses authenticated TLS, the saved policy, and the caller's retry ID", async () => { + const result = await call({ mode: "mutate" }); + expect(result.result.policy.access).to.equal("restricted-review"); + expect(result.frozen).to.equal(true); + expect(result.serialized).to.equal("{}"); + expect(calls).to.have.length(1); + expect(calls[0].body).to.deep.equal(input()); + expect(calls[0].method).to.equal("POST"); + expect(calls[0].url).to.equal("/service/v1/artifact-intents/consume"); + expect(calls[0].headers.authorization).to.equal("Bearer " + "b".repeat(64)); + expect(calls[0].headers["x-4open-artifact-client-id"]).to.equal(clientId); + expect(calls[0].headers["x-4open-artifact-service-key-id"]).to.equal( + "rotation-1", + ); + for (const key of ["cookie", "origin", "referer"]) + expect(calls[0].headers[key]).to.equal(undefined); + await call(); + expect(calls[1].body.requestId).to.equal(calls[0].body.requestId); + }); + it("rejects invalid config and caller data before making a request", async () => { + for (const patch of [ + { origin: origin + "/" }, + { origin: "http://127.0.0.1" }, + { origin: origin + "/path" }, + { token: "bad" }, + { notAfter: at(-1000) }, + { notBefore: at(30000) }, + { clientId: "bad" }, + { extra: "ignored?" }, + ]) + expect((await call({ config: { ...config(), ...patch } })).kind).to.equal( + "invalid", + ); + for (const patch of [ + { clientId: "0".repeat(32) }, + { token: "bad" }, + { requestId: "bad" }, + { policy: {} }, + { contract: "other" }, + ]) + expect((await call({ input: { ...input(), ...patch } })).kind).to.equal( + "invalid", + ); + expect((await call({ mode: "preabort" })).kind).to.equal("unavailable"); + expect(calls).to.have.length(0); + }); + it("classifies failures without following redirects, retrying, or exposing response bodies", async () => { + for (const [status, kind] of [ + [301, "protocol"], + [400, "rejected"], + [401, "rejected"], + [403, "rejected"], + [404, "not-found"], + [409, "conflict"], + [410, "expired"], + [422, "rejected"], + [429, "unavailable"], + [503, "unavailable"], + ]) { + handler = (_req, res) => { + res.writeHead(status, { + Location: origin + "/leak", + "Content-Length": "10000000", + }); + res.write("private response and token"); + }; + const result = await call(); + expect(result.kind).to.equal(kind); + expect(result.message).to.equal("Review intent consumption " + kind); + } + expect(calls).to.have.length(10); + }); + it("rejects mismatched identity, policy, expiry, unknown fields and malformed JSON", async () => { + const good = intent(); + const values = [ + { ...good, clientId: "0".repeat(32) }, + { ...good, intentId: "0".repeat(32) }, + { ...good, callbackId: "bad" }, + { ...good, entitlementId: "bad" }, + { ...good, expiresAt: at(-1000) }, + { ...good, expiresAt: at(700000) }, + { ...good, policy: { ...good.policy, access: "public" } }, + { ...good, policy: { ...good.policy, version: 1.5 } }, + { ...good, policy: { ...good.policy, retainUntil: at(-1000) } }, + { ...good, owner: "browser" }, + ]; + const raw = [ + ...values.map((v) => JSON.stringify(v)), + "null", + "[]", + "{", + JSON.stringify(good).replace( + '"clientId":', + '"clientId":"' + clientId + '","cl\\u0069entId":', + ), + JSON.stringify(good).replace('"version":1', '"version":2,"version":1'), + Buffer.from([0xff]), + " ".repeat(65537), + ]; + for (const body of raw) { + handler = (_req, res) => { + res.setHeader("Content-Type", "application/json"); + res.end(body); + }; + expect((await call()).kind).to.equal("protocol"); + } + }); + it("rejects cookies, encoding, ambiguous content types and oversized declared bodies", async () => { + for (const headers of [ + { "Set-Cookie": "identity=secret" }, + { "Content-Encoding": "gzip" }, + { "Content-Type": "text/html" }, + { "Content-Type": ["application/json", "application/json"] }, + { "Content-Length": "65537" }, + ]) { + handler = (_req, res) => { + res.writeHead(200, { "Content-Type": "application/json", ...headers }); + res.write(JSON.stringify(intent())); + }; + expect((await call()).kind).to.equal("protocol"); + } + }); + it("bounds concurrent attempts and releases capacity after completion", async () => { + handler = (_req, res) => + setTimeout(() => { + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(intent())); + }, 150); + const result = await call({ mode: "parallel" }); + expect(result.results).to.deep.equal(["ok", "ok", "ok", "ok", "busy"]); + expect(result.followup.intentId).to.equal(intentId); + expect(calls).to.have.length(5); + }); + it("cancels an unfinished response without retrying", async () => { + handler = (_req, res) => { + res.writeHead(200, { "Content-Type": "application/json" }); + res.write("{"); + }; + expect((await call({ mode: "abort" })).kind).to.equal("unavailable"); + expect(calls).to.have.length(1); + }); + it("times out an inactive response within its request deadline", async () => { + handler = (_req, res) => { + res.writeHead(200, { "Content-Type": "application/json" }); + res.write("{"); + }; + const start = Date.now(); + expect((await call()).kind).to.equal("unavailable"); + expect(Date.now() - start).to.be.within(4500, 12000); + expect(calls).to.have.length(1); + }); + it("rejects an untrusted TLS certificate before sending credentials", async () => { + const env = { + ...process.env, + NODE_EXTRA_CA_CERTS: "", + NODE_TLS_REJECT_UNAUTHORIZED: "1", + }; + delete env.NODE_OPTIONS; + const untrusted = fork( + path.join(__dirname, "fixtures/review-intent-client.js"), + { env, stdio: ["ignore", "ignore", "pipe", "ipc"] }, + ); + try { + const response = new Promise((resolve) => + untrusted.once("message", resolve), + ); + untrusted.send({ id: 1, config: config(), input: input() }); + expect((await response).kind).to.equal("unavailable"); + expect(calls).to.have.length(0); + } finally { + untrusted.kill(); + } + }); + it("rejects a response received after the credential expires", async () => { + handler = (_req, res) => + setTimeout(() => { + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(intent())); + }, 2100); + expect( + (await call({ config: { ...config(), notAfter: at(2000) } })).kind, + ).to.equal("unavailable"); + expect(calls).to.have.length(1); + }); + it("enforces the total deadline even while the peer streams data", async () => { + handler = (_req, res) => { + res.writeHead(200, { "Content-Type": "application/json" }); + res.write(" "); + const timer = setInterval(() => res.write(" "), 200); + res.once("close", () => clearInterval(timer)); + }; + const start = Date.now(); + expect((await call()).kind).to.equal("unavailable"); + expect(Date.now() - start).to.be.within(9500, 15000); + expect(calls).to.have.length(1); + }); + it("measures repeated fresh TLS connections when explicitly requested", async function () { + if (!process.env.TEST_REVIEW_INTENT_PERF) this.skip(); + const times = []; + for (let n = 0; n < 100; n++) { + const result = await call(); + expect(result.result.intentId).to.equal(intentId); + times.push(result.elapsedMs); + } + times.sort((a, b) => a - b); + const receipt = { + calls: calls.length, + transport: "fresh loopback HTTPS connections", + synthetic: true, + meanMs: times.reduce((a, b) => a + b, 0) / times.length, + medianMs: times[50], + p95Ms: times[94], + }; + expect(calls).to.have.length(100); + if (process.env.TEST_REVIEW_INTENT_PERF_REPORT) + fs.writeFileSync( + process.env.TEST_REVIEW_INTENT_PERF_REPORT, + JSON.stringify(receipt, null, 2) + "\n", + ); + }); +});