feat: contain DTO mapping at the HTTP boundary
This commit is contained in:
@@ -46,7 +46,7 @@ describe("shared HTTP client", () => {
|
||||
client.execute("LIST_SAMPLE_RESOURCES", { routeId: "SAMPLE_RESOURCE_LIST" }),
|
||||
).resolves.toMatchObject({
|
||||
ok: true,
|
||||
value: [{ id: "resource-1" }],
|
||||
value: [{ id: "resource-1", displayName: "Example" }],
|
||||
meta: { requestId: "request-1" },
|
||||
});
|
||||
expect(attempts).toBe(3);
|
||||
@@ -99,4 +99,30 @@ describe("shared HTTP client", () => {
|
||||
error: { kind: "SCHEMA_MISMATCH" },
|
||||
});
|
||||
});
|
||||
|
||||
it("guards mapper exceptions as UNKNOWN_FAILURE", async () => {
|
||||
server.use(
|
||||
http.get("https://api.test/api/sample/resources", () =>
|
||||
HttpResponse.json({
|
||||
success: true,
|
||||
data: [{ id: "resource-1", name: "Example" }],
|
||||
meta: { requestId: "request-1", traceId: "trace-1" },
|
||||
}),
|
||||
),
|
||||
);
|
||||
const client = createHttpClient({
|
||||
baseUrl: "https://api.test",
|
||||
clock,
|
||||
mapPayload: () => {
|
||||
throw new Error("raw mapper detail");
|
||||
},
|
||||
});
|
||||
|
||||
const result = await client.execute("LIST_SAMPLE_RESOURCES");
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { kind: "UNKNOWN_FAILURE" },
|
||||
});
|
||||
expect(JSON.stringify(result)).not.toContain("raw mapper detail");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
mapOperationPayload,
|
||||
mapResourceDto,
|
||||
} from "../../src/adapters/http/resource-mapper.js";
|
||||
import { toResourceViewModel } from "../../src/application/view-models/resource-view-model.js";
|
||||
|
||||
describe("DTO to model to view-model mapping", () => {
|
||||
it("contains raw DTO names at the HTTP boundary", () => {
|
||||
const model = mapResourceDto({
|
||||
id: "resource-1",
|
||||
name: "Example",
|
||||
createdAt: "2026-07-25T00:00:00.000Z",
|
||||
backendOnly: "not propagated",
|
||||
});
|
||||
|
||||
expect(model).toEqual({
|
||||
id: "resource-1",
|
||||
displayName: "Example",
|
||||
createdAt: "2026-07-25T00:00:00.000Z",
|
||||
});
|
||||
expect(model).not.toHaveProperty("name");
|
||||
expect(model).not.toHaveProperty("backendOnly");
|
||||
});
|
||||
|
||||
it("maps operation payloads and rejects missing mappers", () => {
|
||||
expect(
|
||||
mapOperationPayload("LIST_SAMPLE_RESOURCES", [
|
||||
{ id: "resource-1", name: "Example" },
|
||||
]),
|
||||
).toEqual([
|
||||
{ id: "resource-1", displayName: "Example", createdAt: null },
|
||||
]);
|
||||
expect(() => mapOperationPayload("UNKNOWN", {})).toThrow(
|
||||
"No boundary mapper registered",
|
||||
);
|
||||
});
|
||||
|
||||
it("projects an application-owned render-ready shape", () => {
|
||||
const model = mapResourceDto({
|
||||
id: "resource-1",
|
||||
name: "Example",
|
||||
createdAt: null,
|
||||
});
|
||||
expect(toResourceViewModel(model)).toEqual({
|
||||
resourceId: "resource-1",
|
||||
title: "Example",
|
||||
createdAtLabel: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user