feat: add TechLog Studio error mapping and CSRF token provider
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
|
||||
import {
|
||||
STUDIO_ERROR_CODES,
|
||||
toStudioGatewayError,
|
||||
} from "../../../src/features/tech-log/adapters/http/studio-error-mapping.ts";
|
||||
import { createCsrfTokenProvider } from "../../../src/features/tech-log/adapters/http/studio-session-csrf.ts";
|
||||
import { isStudioGatewayError } from "../../../src/features/tech-log/application/ports/studio-gateway-error.ts";
|
||||
|
||||
test("covers every canonical error code exactly once", () => {
|
||||
assert.equal(STUDIO_ERROR_CODES.length, 23);
|
||||
assert.equal(new Set(STUDIO_ERROR_CODES).size, 23);
|
||||
for (const code of ["IDEMPOTENCY_KEY_REUSED", "WARNING_ACKNOWLEDGEMENT_REQUIRED", "ASSET_QUARANTINED"]) {
|
||||
assert.ok(STUDIO_ERROR_CODES.includes(code as never), `${code} is missing`);
|
||||
}
|
||||
});
|
||||
|
||||
test("maps a PROBLEM outcome onto the port error, preserving the code", () => {
|
||||
const error = toStudioGatewayError(
|
||||
{
|
||||
kind: "PROBLEM",
|
||||
problem: {
|
||||
type: "https://techlog.local/problems/version-conflict",
|
||||
title: "VERSION_CONFLICT",
|
||||
status: 409,
|
||||
detail: "Expected 3; current 4.",
|
||||
code: "VERSION_CONFLICT",
|
||||
},
|
||||
metadata: { status: 409 },
|
||||
effect: "NOT_APPLIED",
|
||||
} as never,
|
||||
"saveStudioDocument",
|
||||
);
|
||||
|
||||
assert.ok(isStudioGatewayError(error));
|
||||
assert.equal(error.code, "VERSION_CONFLICT");
|
||||
assert.equal(error.status, 409);
|
||||
});
|
||||
|
||||
test("maps a transport failure onto STUDIO_UNAVAILABLE without inventing a domain code", () => {
|
||||
const error = toStudioGatewayError(
|
||||
{ kind: "TRANSPORT_FAILURE", failure: { kind: "TIMEOUT" }, effect: "MAYBE_APPLIED" } as never,
|
||||
"getStudioDashboard",
|
||||
);
|
||||
|
||||
assert.equal(error.code, "STUDIO_UNAVAILABLE");
|
||||
assert.equal(error.retryable, true);
|
||||
});
|
||||
|
||||
test("maps UNAUTHENTICATED onto AUTHENTICATION_REQUIRED", () => {
|
||||
const error = toStudioGatewayError(
|
||||
{ kind: "UNAUTHENTICATED", effect: "NOT_APPLIED" } as never,
|
||||
"getStudioDashboard",
|
||||
);
|
||||
|
||||
assert.equal(error.code, "AUTHENTICATION_REQUIRED");
|
||||
assert.equal(error.status, 401);
|
||||
});
|
||||
|
||||
test("fetches the CSRF token once and reuses it until invalidated", async () => {
|
||||
let calls = 0;
|
||||
const provider = createCsrfTokenProvider({
|
||||
async execute() {
|
||||
calls += 1;
|
||||
return { csrfToken: `token-${calls}`, csrfHeaderName: "X-CSRF-TOKEN" };
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(await provider.token(), "token-1");
|
||||
assert.equal(await provider.token(), "token-1");
|
||||
assert.equal(calls, 1);
|
||||
|
||||
provider.invalidate();
|
||||
assert.equal(await provider.token(), "token-2");
|
||||
assert.equal(calls, 2);
|
||||
});
|
||||
|
||||
test("does not stampede concurrent CSRF requests", async () => {
|
||||
let calls = 0;
|
||||
const provider = createCsrfTokenProvider({
|
||||
async execute() {
|
||||
calls += 1;
|
||||
await Promise.resolve();
|
||||
return { csrfToken: "token", csrfHeaderName: "X-CSRF-TOKEN" };
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all([provider.token(), provider.token(), provider.token()]);
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user