fix: reject invalid keyed mutation intents
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createHttpClient } from "../../src/adapters/http/client.ts";
|
||||
import { createContractHttpExecutor } from "../../src/adapters/http/http-execution-v3.ts";
|
||||
import {
|
||||
entityQueryKeys,
|
||||
TEST_HTTP_CONTRACT,
|
||||
} from "../helpers/http-contract-fixture.ts";
|
||||
import { TEST_CREATE_HTTP_CONTRACT } from "../helpers/external-contract-fixture.ts";
|
||||
|
||||
function successResponse(data: unknown) {
|
||||
return Response.json({
|
||||
@@ -48,6 +50,79 @@ function testClient(options: HttpDependencies) {
|
||||
}
|
||||
|
||||
describe("HTTP operation execution contract", () => {
|
||||
it("permits a keyless command intent but rejects an unexpected key before dispatch", async () => {
|
||||
const attachCredentials = vi.fn(() => ({
|
||||
kind: "READY" as const,
|
||||
headers: {},
|
||||
credentials: "omit" as const,
|
||||
}));
|
||||
const observedKeys: Array<string | null> = [];
|
||||
const fetcher = vi.fn(
|
||||
async (_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
observedKeys.push(new Headers(init?.headers).get("Idempotency-Key"));
|
||||
return Response.json(
|
||||
{ id: "created", name: "Created" },
|
||||
{ status: 201 },
|
||||
);
|
||||
},
|
||||
);
|
||||
const executor = createContractHttpExecutor({
|
||||
baseUrl: "https://api.example/",
|
||||
maxRetryAttempts: 0,
|
||||
attachCredentials,
|
||||
fetcher,
|
||||
});
|
||||
const nonKeyedCommand = {
|
||||
...TEST_CREATE_HTTP_CONTRACT,
|
||||
contract: {
|
||||
...TEST_CREATE_HTTP_CONTRACT.contract,
|
||||
retrySemantics: "NEVER" as const,
|
||||
commandRecovery: null,
|
||||
},
|
||||
};
|
||||
const scope = Object.freeze({
|
||||
generation: 1,
|
||||
fingerprint: "scope-1",
|
||||
identities: Object.freeze({}) as never,
|
||||
signal: new AbortController().signal,
|
||||
isCurrent: () => true,
|
||||
});
|
||||
const intent = Object.freeze({
|
||||
intentId: "intent-1",
|
||||
operationId: "TEST_CREATE_ENTITY",
|
||||
canonicalInputIdentity: "opaque-input-identity",
|
||||
createdAtMonotonicMs: 1,
|
||||
});
|
||||
|
||||
await expect(
|
||||
executor.execute(
|
||||
nonKeyedCommand,
|
||||
{ name: "created" },
|
||||
{ scope, intent },
|
||||
),
|
||||
).resolves.toMatchObject({ kind: "SUCCESS" });
|
||||
expect(observedKeys).toEqual([null]);
|
||||
|
||||
attachCredentials.mockClear();
|
||||
fetcher.mockClear();
|
||||
await expect(
|
||||
executor.execute(
|
||||
nonKeyedCommand,
|
||||
{ name: "created" },
|
||||
{ scope, intent: { ...intent, idempotencyKey: "unexpected-key" } },
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
kind: "CONTRACT_VIOLATION",
|
||||
violation: {
|
||||
kind: "UNEXPECTED_IDEMPOTENCY_KEY",
|
||||
operation: "REQUEST",
|
||||
},
|
||||
effect: "NOT_STARTED",
|
||||
});
|
||||
expect(attachCredentials).not.toHaveBeenCalled();
|
||||
expect(fetcher).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fails auth-required execution before fetch when the session integration is unavailable", async () => {
|
||||
const fetcher = vi.fn();
|
||||
const client = testClient({
|
||||
|
||||
Reference in New Issue
Block a user