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 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 }, ]; const gatewayMethodNames = [ "getDashboard", "listDocuments", "createDocument", "getDocument", "saveDocument", "validateDocument", "createPreview", "getCurrentPreview", "publishDocument", "unpublishPublication", "listPublications", "getPublicationSnapshot", "getCatalog", ] as const; function gatewayWithEveryRequiredMethod(): StudioGateway { return { getDashboard: async () => undefined as never, listDocuments: async () => undefined as never, createDocument: async () => undefined as never, getDocument: async () => undefined as never, saveDocument: async () => undefined as never, validateDocument: async () => undefined as never, createPreview: async () => undefined as never, getCurrentPreview: async () => undefined as never, publishDocument: async () => undefined as never, unpublishPublication: async () => undefined as never, listPublications: async () => undefined as never, getPublicationSnapshot: async () => undefined as never, getCatalog: async () => undefined as never, }; } 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("requires the exact Studio gateway operation set", () => { expect(Object.keys(gatewayWithEveryRequiredMethod()).sort()).toEqual( [...gatewayMethodNames].sort(), ); }); 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: "DOCUMENT_VERSION_CONFLICT", retryable: false, }); expect(error).toMatchObject({ name: "StudioGatewayError", status: 409, code: "DOCUMENT_VERSION_CONFLICT", retryable: false, }); expect(isStudioGatewayError(error)).toBe(true); expect(isStudioGatewayError(new DOMException("Aborted", "AbortError"))).toBe(false); }); });