Files
tech-log-frontend/tests/mocks/handlers/tech-log-studio.ts
T
DongHyeonkaandClaude Opus 5 1d01a522de feat: implement the TechLog Studio HTTP gateway
Adds createHttpStudioGateway, the adapter the Studio UI calls in
production. It drives every mutation through mutationIntent()
(defineMutationIntent/defineIdempotencyKey, not
createBrowserMutationIntentFactory, so the caller-supplied idempotency
key is preserved rather than regenerated) and leaves header
construction to the executor/credential collaborator entirely - the
gateway never sees or sets Idempotency-Key or x-csrf-token itself.

Also moves stable-stringify.ts out of adapters/mock/ so the mock and
http adapters share one pure function without production code
depending on the mock directory, and fixes the resulting import in
cursor.ts, mock-studio-gateway.ts, and mock-studio-gateway.test.ts.

Adds MSW handlers (tests/mocks/handlers/tech-log-studio.ts) that wrap
the reference mock-studio-gateway implementation, and a contract test
suite that drives the gateway through a thin fetch-based executor
built from the contract's own projectRequest, proving canonical
path/body/header construction without assembling the full platform
transport.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 01:30:24 +09:00

148 lines
4.8 KiB
TypeScript

import { http, HttpResponse } from "msw";
import { createMockStudioGateway } from "../../../src/features/tech-log/adapters/mock/mock-studio-gateway.ts";
import { isStudioGatewayError } from "../../../src/features/tech-log/application/ports/studio-gateway-error.ts";
const BASE = "http://api.test";
function problemResponse(error: unknown) {
if (isStudioGatewayError(error)) {
return HttpResponse.json(error.problem, {
status: error.status,
headers: { "content-type": "application/problem+json" },
});
}
throw error;
}
async function json(work: () => Promise<unknown>, status = 200) {
try {
return HttpResponse.json((await work()) as never, { status });
} catch (error) {
return problemResponse(error);
}
}
export function createTechLogStudioHandlers(
gateway = createMockStudioGateway(),
baseUrl = BASE,
) {
const key = (request: Request) =>
request.headers.get("Idempotency-Key") ?? "missing-key";
return {
gateway,
handlers: [
http.get(`${baseUrl}/api/v1/studio/session`, () =>
HttpResponse.json({
authenticated: true,
displayName: "테스트 편집자",
roles: ["STUDIO_EDITOR"],
csrfToken: "csrf-test-token",
csrfHeaderName: "X-CSRF-TOKEN",
}),
),
http.get(`${baseUrl}/api/v1/studio/dashboard`, () =>
json(() => gateway.getDashboard()),
),
http.get(`${baseUrl}/api/v1/studio/documents`, ({ request }) => {
const url = new URL(request.url);
const limit = url.searchParams.get("limit");
return json(() =>
gateway.listDocuments({
...(url.searchParams.get("q") ? { q: url.searchParams.get("q")! } : {}),
...(limit ? { limit: Number(limit) } : {}),
} as never),
);
}),
http.post(`${baseUrl}/api/v1/studio/documents`, async ({ request }) =>
json(
async () =>
gateway.createDocument((await request.json()) as never, {
idempotencyKey: key(request),
}),
201,
),
),
http.get(`${baseUrl}/api/v1/studio/documents/:documentId`, ({ params }) =>
json(() => gateway.getDocument(String(params.documentId))),
),
http.put(
`${baseUrl}/api/v1/studio/documents/:documentId`,
async ({ request, params }) =>
json(async () =>
gateway.saveDocument(
String(params.documentId),
(await request.json()) as never,
{ idempotencyKey: key(request) },
),
),
),
http.post(
`${baseUrl}/api/v1/studio/documents/:documentId/validate`,
async ({ request, params }) =>
json(async () =>
gateway.validateDocument(
String(params.documentId),
(await request.json()) as never,
{ idempotencyKey: key(request) },
),
),
),
http.get(
`${baseUrl}/api/v1/studio/documents/:documentId/preview`,
({ params }) => json(() => gateway.getCurrentPreview(String(params.documentId))),
),
http.post(
`${baseUrl}/api/v1/studio/documents/:documentId/preview`,
async ({ request, params }) =>
json(
async () =>
gateway.createPreview(
String(params.documentId),
(await request.json()) as never,
{ idempotencyKey: key(request) },
),
201,
),
),
http.post(
`${baseUrl}/api/v1/studio/documents/:documentId/publish`,
async ({ request, params }) =>
json(async () =>
gateway.publishDocument(
String(params.documentId),
(await request.json()) as never,
{ idempotencyKey: key(request) },
),
),
),
http.get(`${baseUrl}/api/v1/studio/publications`, () =>
json(() => gateway.listPublications({})),
),
http.post(
`${baseUrl}/api/v1/studio/publications/:publicationId/unpublish`,
async ({ request, params }) =>
json(async () =>
gateway.unpublishPublication(
String(params.publicationId),
(await request.json()) as never,
{ idempotencyKey: key(request) },
),
),
),
http.get(
`${baseUrl}/api/v1/studio/publications/:publicationEventId/preview`,
({ params }) =>
json(() => gateway.getPublicationSnapshot(String(params.publicationEventId))),
),
http.get(`${baseUrl}/api/v1/studio/catalog`, ({ request }) => {
const url = new URL(request.url);
return json(() =>
gateway.getCatalog({ type: url.searchParams.get("type") as never }),
);
}),
],
};
}