studio-v1.yaml v3.0.0(ADR-006)에 맞춰 계약을 재생성하고, 성공은
{success,data,meta}, 실패는 {success,error,meta} 봉투를 전송 경계에서
언랩하는 envelopeData/envelopeError validator를 도입한다. 앱·도메인
계층은 기존과 같은 payload/ProblemDetails 모양을 계속 받고,
StudioGateway 포트 시그니처는 무변경이다.
- tech-log-studio-contract-contribution.ts: envelopeData/envelopeError
도입, 18개 operation의 outputValidator를 passthrough에서 envelopeData로
교체
- studio-error-mapping.ts: 봉투 오류의 status(항상 0)를
outcome.metadata.status로 덮는다. SafeResponseMetadata.status가
실제 필드명이며(httpStatus 아님) PROBLEM outcome에서 필수 필드다
- contract.ts: 삭제된 ProblemDetails 생성 스키마를 손으로 유지 — 앱
계층·mock 게이트웨이가 그 모양을 계속 소비한다
- asset-upload-transport.ts: multipart 업로드는 일반 계약 런타임을
거치지 않는 별도 seam이지만 같은 wire 봉투를 쓴다 — envelopeData/
envelopeError를 재사용해 이 경로도 언랩한다 (브리프 파일 목록 밖의
발견, report에 기록)
- 테스트: 신규 studio-envelope-unwrap.test.ts(TDD) + 봉투 뼈대를 직접
만드는 기존 테스트(asset-upload-transport, studio-csrf-composition,
contract-generation)를 봉투 형태로 갱신
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
43 lines
1.8 KiB
TypeScript
43 lines
1.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { envelopeData, envelopeError } from "../../../src/features/tech-log/contracts/tech-log-studio-contract-contribution.ts";
|
|
|
|
describe("studio 봉투 언랩", () => {
|
|
it("성공 봉투에서 data를 꺼낸다", () => {
|
|
const result = envelopeData("getStudioSessionOutput").safeParse({
|
|
success: true,
|
|
data: { authenticated: true, displayName: "d", roles: [], csrfToken: "t", csrfHeaderName: "X-CSRF-TOKEN" },
|
|
meta: { requestId: "r", traceId: "t", correlationId: null, page: null },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) expect(result.data).toMatchObject({ displayName: "d" });
|
|
});
|
|
|
|
it("봉투가 아닌 본문을 거절한다", () => {
|
|
const result = envelopeData("getStudioSessionOutput").safeParse({ displayName: "d" });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it("오류 봉투를 ProblemDetails 형태로 옮긴다", () => {
|
|
const result = envelopeError().safeParse({
|
|
success: false,
|
|
error: { code: "VERSION_CONFLICT", category: "CONFLICT", message: "conflict", retryable: false, details: null },
|
|
meta: { requestId: "r", traceId: "tr", correlationId: null, page: null },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.code).toBe("VERSION_CONFLICT");
|
|
expect(result.data.status).toBe(0);
|
|
expect(result.data.title).toBe("VERSION_CONFLICT");
|
|
}
|
|
});
|
|
|
|
it("계약 밖 코드를 거절한다", () => {
|
|
const result = envelopeError().safeParse({
|
|
success: false,
|
|
error: { code: "NOT_A_STUDIO_CODE", category: "INTERNAL", message: "x", retryable: false, details: null },
|
|
meta: { requestId: "r", traceId: "tr", correlationId: null, page: null },
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|