Files
tech-log-frontend/tests/features/tech-log/studio-contract.test.ts
T

113 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { components } from "../../../src/features/tech-log/contracts/studio/generated.ts";
import {
isStudioGatewayError,
StudioGatewayError,
} from "../../../src/features/tech-log/application/ports/studio-gateway-error.ts";
import type {
StudioGateway,
} from "../../../src/features/tech-log/application/ports/studio-gateway.ts";
type Equal<Left, Right> =
(<Value>() => Value extends Left ? 1 : 2) extends
(<Value>() => Value extends Right ? 1 : 2)
? true
: false;
type Assert<Condition extends true> = Condition;
type Inline = components["schemas"]["Inline"];
type CaseRenderBlock = components["schemas"]["CaseRenderBlock"];
const inlines: Inline[] = [
{ type: "TEXT", text: "plain" },
{ type: "EMPHASIS", children: [{ type: "TEXT", text: "emphasis" }] },
{ type: "STRONG", children: [{ type: "TEXT", text: "strong" }] },
{ type: "INLINE_CODE", code: "findById" },
{ type: "LINK", label: "TechLog", href: "https://example.com" },
{ type: "STATUS", label: "검증 필요", tone: "warning" },
];
const blocks: CaseRenderBlock[] = [
{ type: "HEADING", id: "problem", level: 2, content: [] },
{ type: "PARAGRAPH", content: [] },
{ type: "BLOCKQUOTE", content: [] },
{ type: "UNORDERED_LIST", items: [] },
{ type: "ORDERED_LIST", items: [] },
{ type: "CODE_BLOCK", code: "select 1", language: "sql", label: null },
{
type: "DATA_TABLE",
id: "query-counts",
caption: "Query counts",
rowHeaderColumn: null,
columns: [{ id: "query-counts-column-1", label: "Case", alignment: "LEFT" }],
rows: [],
},
{ type: "CALLOUT", tone: "warning", label: "주의", content: [] },
{ type: "EVIDENCE_FIGURE", key: "fetch-plan", alt: "Fetch plan", caption: "Measured fetch plan", zoom: true },
];
type RequiredStudioGatewayOperation =
| "getDashboard"
| "listDocuments"
| "createDocument"
| "getDocument"
| "saveDocument"
| "validateDocument"
| "createPreview"
| "getCurrentPreview"
| "publishDocument"
| "unpublishPublication"
| "listPublications"
| "getPublicationSnapshot"
| "getCatalog";
type StudioGatewayExposesNoMissingOrAdditionalOperations = Assert<
Equal<keyof StudioGateway, RequiredStudioGatewayOperation>
>;
describe("TechLog Studio contracts", () => {
it("preserves every public wire discriminator exactly once", () => {
expect(inlines.map((inline) => inline.type)).toEqual([
"TEXT",
"EMPHASIS",
"STRONG",
"INLINE_CODE",
"LINK",
"STATUS",
]);
expect(blocks.map((block) => block.type)).toEqual([
"HEADING",
"PARAGRAPH",
"BLOCKQUOTE",
"UNORDERED_LIST",
"ORDERED_LIST",
"CODE_BLOCK",
"DATA_TABLE",
"CALLOUT",
"EVIDENCE_FIGURE",
]);
expect(new Set(blocks.map((block) => block.type)).size).toBe(blocks.length);
});
it("keeps RFC 9457-style gateway failures inspectable at the application boundary", () => {
const error = new StudioGatewayError({
type: "https://techlog.dev/problems/conflict",
title: "Conflict",
status: 409,
detail: "The working copy has changed.",
code: "VERSION_CONFLICT",
retryable: false,
});
expect(error).toMatchObject({
name: "StudioGatewayError",
status: 409,
code: "VERSION_CONFLICT",
retryable: false,
});
expect(isStudioGatewayError(error)).toBe(true);
expect(isStudioGatewayError(new DOMException("Aborted", "AbortError"))).toBe(false);
});
});