chore: initialize from frontend template 4dc033c

This commit is contained in:
DongHyeonka
2026-08-13 18:23:26 +09:00
commit 40107eec84
897 changed files with 234824 additions and 0 deletions
@@ -0,0 +1,186 @@
import { describe, expect, it, vi } from "vitest";
import {
buildRouteUrl,
parseRouteInput,
} from "../../../src/presentation/routes/route-codecs.ts";
import {
mapReferenceOperation,
toReferenceView,
} from "../../../src/features/reference-feature/contracts/reference-mapper.ts";
import {
validateReferencePayload,
validateReferenceRequest,
} from "../../../src/features/reference-feature/contracts/reference-schemas.ts";
import {
REFERENCE_FEATURE_CONTRACT,
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
} from "../../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import type { ReferenceFeatureInput } from "../../../src/features/reference-feature/application/reference-feature-api.ts";
import { createTestApplication } from "../../helpers/create-test-application.ts";
import { createReferenceFeatureInstalledInput } from "../../../src/features/reference-feature/adapters/create-reference-feature-input.ts";
describe("reference feature boundary contracts", () => {
it("propagates uncertain command effect certainty into the application failure", async () => {
const execute = vi.fn(async () => ({
kind: "CONTRACT_VIOLATION" as const,
violation: {
kind: "SUCCESS_SCHEMA_INVALID" as const,
operation: "VALIDATION" as const,
},
effect: "MAYBE_APPLIED" as const,
}));
const installed = createReferenceFeatureInstalledInput({
contractOperations: { execute },
});
await expect(
installed.input.createResource({ name: "uncertain" }),
).resolves.toMatchObject({
ok: false,
error: {
effect: "MAYBE_APPLIED",
retryable: false,
action: "contact-support",
},
});
});
it("round-trips one canonical filter through the URL codec", () => {
const filters = {
tags: ["open", "new"],
cursor: "a/b",
limit: 5,
};
const url = buildRouteUrl("REFERENCE_RESOURCE_LIST", { search: filters });
expect(url).toBe(
"/examples/reference-resources?cursor=a%2Fb&limit=5&tags=open&tags=new",
);
const parsed = parseRouteInput(
"REFERENCE_RESOURCE_LIST",
{},
new URL(url, "https://app.test").searchParams,
);
expect(parsed).toMatchObject({
success: true,
data: { search: filters },
});
});
it("rejects unknown search and malformed DTO before mapping", () => {
expect(
parseRouteInput(
"REFERENCE_RESOURCE_LIST",
{},
new URLSearchParams("unknown=value"),
),
).toEqual({ success: false, code: "ROUTE_SEARCH_INVALID" });
expect(
validateReferencePayload("ReferenceResourceListPayload", [
{ id: "unsafe", name: 42 },
]),
).toMatchObject({ success: false });
expect(
mapReferenceOperation("LIST_REFERENCE_RESOURCES", [
{ id: "unsafe", name: 42 },
]),
).toMatchObject({ ok: false });
});
it("normalizes request input and maps only owned domain fields", () => {
expect(
validateReferenceRequest("CreateReferenceResourceCommand", {
name: " Example ",
}),
).toMatchObject({ success: true, data: { name: "Example" } });
const model = mapReferenceOperation("CREATE_REFERENCE_RESOURCE", {
id: "reference-1",
name: "Example",
createdAt: "2026-07-26T00:00:00.000Z",
});
if (!model.ok || !("id" in model.value)) {
throw new Error("expected one model");
}
expect(toReferenceView(model.value)).toEqual({
resourceId: "reference-1",
title: "Example",
createdAt: "2026-07-26T00:00:00.000Z",
});
});
it("owns route and operation contributions in one removable contract", () => {
expect(Object.keys(REFERENCE_FEATURE_CONTRACT.routes)).toEqual([
"REFERENCE_RESOURCE_LIST",
"REFERENCE_RESOURCE_DETAIL",
"REFERENCE_RESOURCE_FORM",
"REFERENCE_RESOURCE_STATUS",
]);
expect(Object.keys(REFERENCE_FEATURE_CONTRACT.apiOperations)).toEqual([
"LIST_REFERENCE_RESOURCES",
"CREATE_REFERENCE_RESOURCE",
"GET_REFERENCE_RESOURCE",
]);
expect(
REFERENCE_FEATURE_CONTRACT.apiOperations.GET_REFERENCE_RESOURCE,
).toMatchObject({
pathSchema: "ReferenceResourceParams",
pathParameterNames: ["resourceId"],
providerId: "PRIMARY_API",
authProfileId: "REFERENCE_EXTERNAL_BEARER",
csrfProfileId: "NO_CSRF_BEARER",
});
});
it("contributes an identity-based invalidation graph and an explicit wire version", () => {
expect(REFERENCE_FEATURE_CONTRACT.invalidation).toEqual({
topics: [REFERENCE_RESOURCE_INVALIDATION_TOPIC],
namespaces: [
{ namespaceId: "reference-resource", namespaceVersion: 1 },
],
edges: [
{
topicId: REFERENCE_RESOURCE_INVALIDATION_TOPIC,
namespace: {
namespaceId: "reference-resource",
namespaceVersion: 1,
},
},
],
});
expect(REFERENCE_FEATURE_CONTRACT.topicVersions).toEqual([
{
topicId: REFERENCE_RESOURCE_INVALIDATION_TOPIC,
topicVersion: 1,
},
]);
});
it("returns the installed input through the typed application registry", () => {
const featureInput = {
listResources: async () => ({ ok: true as const, value: [] }),
createResource: async ({ name }) => ({
ok: true as const,
value: {
resourceId: "created",
title: name,
createdAt: null,
},
}),
getResource: async (resourceId) => ({
ok: true as const,
value: {
resourceId,
title: "Reference",
createdAt: null,
},
}),
} satisfies ReferenceFeatureInput;
const application = createTestApplication({
featureInputs: { [REFERENCE_FEATURE_ID]: featureInput },
});
expect(application.features.has(REFERENCE_FEATURE_ID)).toBe(true);
expect(application.features.get(REFERENCE_FEATURE_ID)).toBe(featureInput);
});
});
@@ -0,0 +1,71 @@
import { describe, expect, it, vi } from "vitest";
import { createHttpClient } from "../../../src/adapters/http/client.ts";
import { createDemoSessionAdapter } from "../../../src/adapters/auth/external-session-adapter.ts";
import {
createReferenceHttpGateway,
} from "../../../src/features/reference-feature/adapters/reference-http-gateway.ts";
import { createReferenceFeatureInput } from "../../../src/features/reference-feature/application/reference-feature-api.ts";
import { REFERENCE_FEATURE_CONTRACT } from "../../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import { mapReferenceOperation } from "../../../src/features/reference-feature/contracts/reference-mapper.ts";
import {
validateReferencePayload,
validateReferenceRequest,
} from "../../../src/features/reference-feature/contracts/reference-schemas.ts";
describe("reference feature diagnostics correlation", () => {
it("preserves route, operation and request correlation through the vertical path", async () => {
const record = vi.fn();
const operations =
REFERENCE_FEATURE_CONTRACT.apiOperations as Readonly<
Record<
string,
ReturnType<
NonNullable<Parameters<typeof createHttpClient>[0]["getOperation"]>
>
>
>;
const client = createHttpClient({
baseUrl: "https://api.test",
authSession: createDemoSessionAdapter("authenticated"),
fetcher: async () =>
Response.json({
success: true,
data: [{ id: "reference-1", name: "Reference" }],
meta: { requestId: "safe-request", traceId: "safe-trace" },
}),
getOperation(operationId) {
const operation = operations[operationId];
if (!operation) throw new Error("Unregistered reference operation");
return operation;
},
validatePayload: validateReferencePayload,
validateRequest: validateReferenceRequest,
mapPayload: mapReferenceOperation,
correlationIdFactory: () => "reference-correlation",
diagnostics: { record },
scheduler: {
setTimeout: () => 1,
clearTimeout: () => {},
},
});
const application = createReferenceFeatureInput(
createReferenceHttpGateway(client),
);
await expect(
application.listResources({ limit: 20 }),
).resolves.toMatchObject({ ok: true });
expect(record).toHaveBeenCalledOnce();
expect(record).toHaveBeenCalledWith({
level: "info",
eventId: "http.request.completed",
context: expect.objectContaining({
route_id: "REFERENCE_RESOURCE_LIST",
operation_id: "LIST_REFERENCE_RESOURCES",
correlation_id: "reference-correlation",
outcome: "success",
}),
});
});
});
@@ -0,0 +1,106 @@
import { describe, expect, it, vi } from "vitest";
import { createFailure } from "../../../src/contracts/errors.ts";
import {
createReferenceHttpGateway,
type RawReferenceHttpExecutor,
} from "../../../src/features/reference-feature/adapters/reference-http-gateway.ts";
import type { ReferenceResource } from "../../../src/features/reference-feature/domain/reference-resource.ts";
const resources = Object.freeze({
first: Object.freeze({
id: "reference-1",
displayName: "First",
createdAt: null,
}),
created: Object.freeze({
id: "reference-2",
displayName: "Created",
createdAt: "2026-07-26T00:00:00.000Z",
}),
}) satisfies Readonly<Record<string, ReferenceResource>>;
describe("reference HTTP operation gateway", () => {
it("builds the exact registered request for every gateway operation", async () => {
const execute = vi
.fn<RawReferenceHttpExecutor["execute"]>()
.mockResolvedValueOnce({ ok: true, value: [resources.first] })
.mockResolvedValueOnce({ ok: true, value: resources.created })
.mockResolvedValueOnce({ ok: true, value: resources.first });
const gateway = createReferenceHttpGateway({ execute });
const signal = new AbortController().signal;
await expect(
gateway.list({ cursor: "next", limit: 20, tags: ["active"] }, { signal }),
).resolves.toEqual({ ok: true, value: [resources.first] });
await expect(
gateway.create({ name: "Created", note: "safe note" }),
).resolves.toEqual({ ok: true, value: resources.created });
await expect(
gateway.get("reference-1", { signal }),
).resolves.toEqual({ ok: true, value: resources.first });
expect(execute.mock.calls).toEqual([
[
{
operationId: "LIST_REFERENCE_RESOURCES",
routeId: "REFERENCE_RESOURCE_LIST",
searchParams: {
cursor: "next",
limit: 20,
tags: ["active"],
},
signal,
},
],
[
{
operationId: "CREATE_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_LIST",
body: { name: "Created", note: "safe note" },
},
],
[
{
operationId: "GET_REFERENCE_RESOURCE",
routeId: "REFERENCE_RESOURCE_DETAIL",
pathParams: { resourceId: "reference-1" },
signal,
},
],
]);
});
it("preserves a validated raw failure without casting it into success", async () => {
const failure = createFailure(
"SCHEMA_MISMATCH",
"GET_REFERENCE_RESOURCE",
0,
);
const execute = vi
.fn<RawReferenceHttpExecutor["execute"]>()
.mockResolvedValue({ ok: false, error: failure });
const gateway = createReferenceHttpGateway({ execute });
await expect(gateway.get("invalid")).resolves.toEqual({
ok: false,
error: failure,
});
});
it("fails closed when a raw success does not match its operation result", async () => {
const execute = vi
.fn<RawReferenceHttpExecutor["execute"]>()
.mockResolvedValue({ ok: true, value: { id: "not-a-list" } });
const gateway = createReferenceHttpGateway({ execute });
await expect(gateway.list({ limit: 20 })).resolves.toMatchObject({
ok: false,
error: {
kind: "MAPPING_CONTRACT_VIOLATION",
code: "BOUND_RESULT_TYPE_MISMATCH",
operationId: "LIST_REFERENCE_RESOURCES",
},
});
});
});
@@ -0,0 +1,490 @@
// @vitest-environment jsdom
import {
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { createDemoSessionAdapter } from "../../../src/adapters/auth/external-session-adapter.ts";
import type { AuthSessionPort } from "../../../src/application/ports/auth-session-port.ts";
import type { MutationIntentFactory } from "../../../src/application/ports/mutation-intent-factory.ts";
import type {
ReferenceFeatureInput,
ReferenceResult,
} from "../../../src/features/reference-feature/application/reference-feature-api.ts";
import {
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
REFERENCE_RESOURCE_QUERY_NAMESPACE,
} from "../../../src/features/reference-feature/contracts/reference-feature-contract.ts";
import { INVALIDATION_REGISTRY } from "../../../src/features/installed-feature-contracts.ts";
import type { ReferenceResourceView } from "../../../src/features/reference-feature/contracts/reference-mapper.ts";
import { createFailure } from "../../../src/contracts/errors.ts";
import type { QueryInvalidationCoordinator } from "../../../src/contracts/query-invalidation.ts";
import { QueryInvalidationProvider } from "../../../src/presentation/adapters/query/query-invalidation-provider.tsx";
import { MutationIntentProvider } from "../../../src/presentation/adapters/query/mutation-intent-provider.tsx";
import { ServerStateScopeProvider } from "../../../src/presentation/adapters/query/server-state-scope-provider.tsx";
import { createServerStateScopeRuntime } from "../../../src/adapters/query-cache/server-state-scope-runtime.ts";
import { ApplicationProvider } from "../../../src/presentation/providers/application-provider.tsx";
import { AppRouter } from "../../../src/presentation/routes/app-router.tsx";
import { createTestApplication } from "../../helpers/create-test-application.ts";
function renderReference(
input: ReferenceFeatureInput,
url = "/examples/reference-resources?limit=5",
session: AuthSessionPort = createDemoSessionAdapter("authenticated"),
) {
window.history.pushState({}, "", url);
const client = new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: Infinity },
mutations: { retry: false },
},
});
const invalidation: QueryInvalidationCoordinator = Object.freeze({
async invalidate() {},
beginMutation() {
return Object.freeze({
async release() {},
});
},
async resetLocal() {},
dispose() {},
});
const serverStateScope = createServerStateScopeRuntime({
session,
queryInvalidation: invalidation,
});
let intentSequence = 0;
const mutationIntentFactory: MutationIntentFactory = Object.freeze({
create(input) {
intentSequence += 1;
return Object.freeze({
intentId: `reference-page-intent-${intentSequence}`,
operationId: input.operationId,
canonicalInputIdentity: input.canonicalInputIdentity,
...(input.requiresIdempotencyKey
? { idempotencyKey: `reference-page-key-${intentSequence}` }
: {}),
createdAtMonotonicMs: intentSequence,
});
},
});
return Object.assign(render(
<MutationIntentProvider factory={mutationIntentFactory}>
<QueryClientProvider client={client}>
<ServerStateScopeProvider runtime={serverStateScope}>
<QueryInvalidationProvider coordinator={invalidation}>
<ApplicationProvider
application={createTestApplication({
session,
featureInputs: { [REFERENCE_FEATURE_ID]: input },
})}
>
<AppRouter />
</ApplicationProvider>
</QueryInvalidationProvider>
</ServerStateScopeProvider>
</QueryClientProvider>
</MutationIntentProvider>,
), { client });
}
function inputWith(
overrides: Partial<ReferenceFeatureInput> = {},
): ReferenceFeatureInput {
return {
listResources: async () => ({ ok: true, value: [] }),
createResource: async ({ name }) => ({
ok: true,
value: {
resourceId: "created",
title: name,
createdAt: null,
},
}),
getResource: async (resourceId) => ({
ok: true,
value: {
resourceId,
title: "Detail",
createdAt: null,
},
}),
...overrides,
};
}
describe("reference feature page states", () => {
it("mounts list and detail keys under the installed governed namespace", async () => {
expect(REFERENCE_RESOURCE_QUERY_NAMESPACE).toEqual({
namespaceId: "reference-resource",
namespaceVersion: 1,
});
const installedEdge = INVALIDATION_REGISTRY.edges.find(
(edge) => edge.topicId === REFERENCE_RESOURCE_INVALIDATION_TOPIC,
);
expect(installedEdge?.namespace).toEqual(
REFERENCE_RESOURCE_QUERY_NAMESPACE,
);
const list = renderReference(inputWith());
await screen.findByRole("heading", { name: "표시할 항목이 없습니다." });
const listKey = list.client.getQueryCache().getAll()[0]?.queryKey;
expect(listKey?.slice(0, 4)).toEqual([
"query",
2,
REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceId,
REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceVersion,
]);
list.unmount();
const detail = renderReference(
inputWith(),
"/examples/reference-resources/reference-1",
);
await screen.findByText("Detail");
const detailKey = detail.client.getQueryCache().getAll()[0]?.queryKey;
expect(detailKey?.slice(0, 4)).toEqual([
"query",
2,
installedEdge?.namespace.namespaceId,
installedEdge?.namespace.namespaceVersion,
]);
detail.unmount();
});
it("renders loading, success and empty states through the installed route", async () => {
let resolveList:
| ((result: ReferenceResult<readonly ReferenceResourceView[]>) => void)
| undefined;
const pending = new Promise<
ReferenceResult<readonly ReferenceResourceView[]>
>((resolve) => {
resolveList = resolve;
});
const loaded = renderReference(
inputWith({ listResources: async () => pending }),
);
expect(await screen.findByLabelText("불러오는 중")).toBeVisible();
resolveList?.({
ok: true,
value: [
{
resourceId: "reference-1",
title: "Loaded",
createdAt: null,
},
],
});
expect(await screen.findByText("Loaded")).toBeVisible();
loaded.unmount();
renderReference(inputWith(), "/examples/reference-resources?limit=10");
expect(
await screen.findByRole("heading", {
name: "표시할 항목이 없습니다.",
}),
).toBeVisible();
});
it("rejects invalid URL input before the feature application input", async () => {
const listResources = vi.fn();
renderReference(
inputWith({ listResources }),
"/examples/reference-resources?limit=invalid",
);
expect(
await screen.findByRole("heading", {
name: "올바르지 않은 주소입니다.",
}),
).toBeVisible();
expect(listResources).not.toHaveBeenCalled();
});
it("renders backend forbidden even when the client access hint allowed entry", async () => {
const user = userEvent.setup();
renderReference(
inputWith({
listResources: async () => ({
ok: false,
error: createFailure(
"FORBIDDEN",
"LIST_REFERENCE_RESOURCES",
0,
),
}),
}),
);
expect(await screen.findByRole("alert")).toHaveTextContent(
"이 작업을 수행할 권한이 없습니다.",
);
await user.click(
screen.getByRole("button", { name: "안전한 화면으로 이동" }),
);
expect(
await screen.findByRole("heading", {
level: 1,
name: "Clean Architecture Frontend",
}),
).toBeVisible();
});
it("starts sign-in with the current route for a backend auth failure", async () => {
const user = userEvent.setup();
const demoSession = createDemoSessionAdapter("authenticated");
const beginSignIn = vi.fn(async () => {});
const session = { ...demoSession, beginSignIn };
renderReference(
inputWith({
listResources: async () => ({
ok: false,
error: createFailure(
"AUTH_REQUIRED",
"LIST_REFERENCE_RESOURCES",
0,
),
}),
}),
"/examples/reference-resources?limit=5",
session,
);
await user.click(await screen.findByRole("button", { name: "로그인" }));
expect(beginSignIn).toHaveBeenCalledWith(
"/examples/reference-resources?limit=5",
);
});
it("falls back to a public route when starting sign-in fails", async () => {
const user = userEvent.setup();
const demoSession = createDemoSessionAdapter("authenticated");
const beginSignIn = vi.fn(async () => {
throw new Error("identity provider unavailable");
});
renderReference(
inputWith({
listResources: async () => ({
ok: false,
error: createFailure(
"AUTH_REQUIRED",
"LIST_REFERENCE_RESOURCES",
0,
),
}),
}),
"/examples/reference-resources?limit=5",
{ ...demoSession, beginSignIn },
);
await user.click(await screen.findByRole("button", { name: "로그인" }));
expect(
await screen.findByRole("heading", {
level: 1,
name: "Clean Architecture Frontend",
}),
).toBeVisible();
});
it("deduplicates create, preserves input and surfaces a conflict", async () => {
const user = userEvent.setup();
let finish:
| ((result: ReferenceResult<ReferenceResourceView>) => void)
| undefined;
const createResource = vi.fn(
() =>
new Promise<ReferenceResult<ReferenceResourceView>>((resolve) => {
finish = resolve;
}),
);
renderReference(
inputWith({ createResource }),
"/examples/reference-resources/new",
);
await screen.findByRole("heading", {
name: "Reference resource 만들기",
});
await user.type(
screen.getByRole("textbox", { name: /새 항목 이름/ }),
"Conflicting",
);
await user.type(screen.getByLabelText("설명"), "Keep this input");
const submit = screen.getByRole("button", { name: "저장" });
await user.dblClick(submit);
await waitFor(() => expect(createResource).toHaveBeenCalledOnce());
expect(screen.getByRole("button", { name: "저장 중…" })).toBeDisabled();
finish?.({
ok: false,
error: createFailure(
"CONFLICT",
"CREATE_REFERENCE_RESOURCE",
0,
{ effect: "NOT_APPLIED" },
),
});
expect(await screen.findByText(/다른 변경과 충돌했습니다/)).toBeVisible();
expect(
screen.getByRole("textbox", { name: /새 항목 이름/ }),
).toHaveValue("Conflicting");
expect(screen.getByLabelText("설명")).toHaveValue("Keep this input");
});
it("blocks resubmit and exposes only reconciliation for an unknown create effect", async () => {
const user = userEvent.setup();
const createResource = vi.fn(async () => ({
ok: false as const,
error: createFailure(
"SERVER_FAILURE",
"CREATE_REFERENCE_RESOURCE",
0,
{ effect: "MAYBE_APPLIED" },
),
}));
renderReference(
inputWith({ createResource }),
"/examples/reference-resources/new",
);
await user.type(
await screen.findByRole("textbox", { name: /새 항목 이름/ }),
"Unknown result",
);
await user.click(screen.getByRole("button", { name: "저장" }));
expect(
await screen.findByText("변경 결과를 확인할 수 없습니다."),
).toBeVisible();
expect(screen.getByRole("button", { name: "저장" })).toBeDisabled();
expect(
screen.queryByText("저장하지 못했습니다. 잠시 후 다시 시도해 주세요."),
).not.toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "저장" }));
expect(createResource).toHaveBeenCalledOnce();
expect(
screen.getByRole("textbox", { name: /새 항목 이름/ }),
).toHaveValue("Unknown result");
await user.click(
screen.getByRole("button", { name: "변경되지 않음으로 확인" }),
);
await waitFor(() =>
expect(screen.getByRole("button", { name: "저장" })).toBeEnabled(),
);
expect(createResource).toHaveBeenCalledOnce();
expect(
screen.getByRole("textbox", { name: /새 항목 이름/ }),
).toHaveValue("Unknown result");
});
it("settles the form after confirming an unknown create was applied", async () => {
const user = userEvent.setup();
const createResource = vi.fn(async () => ({
ok: false as const,
error: createFailure(
"SERVER_FAILURE",
"CREATE_REFERENCE_RESOURCE",
0,
{ effect: "MAYBE_APPLIED" },
),
}));
renderReference(
inputWith({ createResource }),
"/examples/reference-resources/new",
);
const name = await screen.findByRole("textbox", {
name: /새 항목 이름/,
});
await user.type(name, "Already created");
await user.click(screen.getByRole("button", { name: "저장" }));
await user.click(
await screen.findByRole("button", { name: "변경됨으로 확인" }),
);
expect(await screen.findByRole("status")).toHaveTextContent("저장했습니다.");
expect(name).toHaveValue("");
await user.click(screen.getByRole("button", { name: "저장" }));
expect(createResource).toHaveBeenCalledOnce();
});
it("treats an applied-confirmed failure as a settled create", async () => {
const user = userEvent.setup();
const createResource = vi.fn(async () => ({
ok: false as const,
error: createFailure(
"SERVER_FAILURE",
"CREATE_REFERENCE_RESOURCE",
0,
{ effect: "APPLIED_CONFIRMED" },
),
}));
renderReference(
inputWith({ createResource }),
"/examples/reference-resources/new",
);
const name = await screen.findByRole("textbox", {
name: /새 항목 이름/,
});
await user.type(name, "Committed despite response");
await user.click(screen.getByRole("button", { name: "저장" }));
expect(await screen.findByRole("status")).toHaveTextContent("저장했습니다.");
expect(name).toHaveValue("");
expect(
screen.queryByText("저장하지 못했습니다. 잠시 후 다시 시도해 주세요."),
).not.toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "저장" }));
expect(createResource).toHaveBeenCalledOnce();
});
it("keeps stale data visible during refresh failure and recovers on retry", async () => {
const user = userEvent.setup();
const listResources = vi
.fn<ReferenceFeatureInput["listResources"]>()
.mockResolvedValueOnce({
ok: true,
value: [
{
resourceId: "existing",
title: "Existing",
createdAt: null,
},
],
})
.mockResolvedValueOnce({
ok: false,
error: createFailure(
"SERVER_FAILURE",
"LIST_REFERENCE_RESOURCES",
0,
),
})
.mockResolvedValue({
ok: true,
value: [
{
resourceId: "recovered",
title: "Recovered",
createdAt: null,
},
],
});
renderReference(inputWith({ listResources }));
await screen.findByText("Existing");
await user.click(screen.getByRole("button", { name: "새로고침" }));
expect(
await screen.findByText("기존 정보를 표시하고 있습니다."),
).toBeVisible();
expect(screen.getByText("Existing")).toBeVisible();
await user.click(screen.getByRole("button", { name: "다시 시도" }));
expect(await screen.findByText("Recovered")).toBeVisible();
await waitFor(() =>
expect(screen.queryByText("stale-degraded")).not.toBeInTheDocument(),
);
expect(listResources).toHaveBeenCalledTimes(3);
});
});
@@ -0,0 +1,121 @@
// @vitest-environment jsdom
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
vi,
} from "vitest";
import { createRuntimeComposition } from "../../../src/bootstrap/create-runtime-composition.ts";
import { RuntimeApplication } from "../../../src/bootstrap/runtime-application.tsx";
import { createBootstrapHandlers } from "../../mocks/handlers/bootstrap.ts";
import { createReferenceScenarioHandlers } from "../../mocks/handlers/reference-resources.ts";
import { createStrictMockServer } from "../../mocks/server.ts";
const runtimeConfig = {
APP_ENV: "local",
API_BASE_URL: "https://api.test",
REQUEST_TIMEOUT_MS: 10_000,
MAX_RETRY_ATTEMPTS: 0,
TELEMETRY_ENABLED: false,
AUTH_MODE: "demo",
CONFIG_SCHEMA_VERSION: "1",
API_CONTRACT_VERSION: "1",
RELEASE_MANIFEST_URL: "/release-manifest.json",
BUILD_ID: "local-build",
RELEASE_ID: "local-release",
};
const releaseManifest = {
schemaVersion: 1,
appVersion: "0.1.0",
buildId: "local-build",
commitSha: "local",
configSchemaVersion: "1",
apiContractVersion: "1",
assetManifestHash: "test-hash",
releaseId: "local-release",
builtAt: "2026-07-26T00:00:00.000Z",
routeChunks: {
"route-home": "assets/home.js",
"route-examples-ui": "assets/ui.js",
"route-examples-states": "assets/states.js",
"route-examples-auth": "assets/auth.js",
"route-reference-resources": "assets/reference.js",
"route-reference-resource-detail": "assets/reference-detail.js",
"route-reference-resource-form": "assets/reference-form.js",
"route-reference-resource-status": "assets/reference-status.js",
"route-not-found": "assets/not-found.js",
},
};
const listRequests = vi.fn();
const createRequests = vi.fn();
const resources = [{ id: "reference-1", name: "Existing" }];
const mockApi = createStrictMockServer(
...createBootstrapHandlers(runtimeConfig, releaseManifest),
...createReferenceScenarioHandlers({
resources,
onList: listRequests,
onCreate: createRequests,
}),
);
beforeAll(mockApi.listen);
afterEach(() => {
mockApi.reset();
listRequests.mockClear();
createRequests.mockClear();
resources.splice(1);
});
afterAll(mockApi.close);
const absoluteFetch: typeof fetch = (input, init) => {
if (input instanceof Request) return fetch(input, init);
const url = new URL(
input instanceof URL ? input.href : input,
"http://app.test",
);
return fetch(url, init);
};
describe("reference feature production vertical path", () => {
it("traverses bootstrap, router, application, HTTP schema/mapper and query cache", async () => {
const user = userEvent.setup();
const composition = await createRuntimeComposition({
fetcher: absoluteFetch,
host: {},
});
window.history.pushState(
{},
"",
"/examples/reference-resources?tags=open&tags=new&limit=5",
);
render(<RuntimeApplication composition={composition} />);
await user.click(
await screen.findByRole("button", { name: "로그인 시작" }),
);
expect(await screen.findByText("Existing")).toBeVisible();
expect(listRequests).toHaveBeenCalledWith(
"?limit=5&tags=open&tags=new",
);
await user.click(screen.getByRole("button", { name: "새 항목 만들기" }));
await user.type(
await screen.findByRole("textbox", { name: /새 항목 이름/ }),
" Created ",
);
await user.click(screen.getByRole("button", { name: "저장" }));
expect(await screen.findByText("저장했습니다.")).toBeVisible();
await user.click(screen.getByRole("button", { name: "목록으로 돌아가기" }));
expect(await screen.findByText("Created")).toBeVisible();
expect(createRequests).toHaveBeenCalledWith({ name: "Created" });
expect(listRequests.mock.calls.length).toBeGreaterThanOrEqual(2);
});
});
@@ -0,0 +1,195 @@
import { describe, expect, it, vi } from "vitest";
import { createRuntimeAdapters } from "../../../src/bootstrap/runtime-adapters.ts";
import { createRuntimeIdentityRegistry } from "../../../src/contracts/query-keys.ts";
import { bindQuery } from "../../../src/contracts/server-state.ts";
import type { CacheScopeSnapshot } from "../../../src/contracts/server-state-scope.ts";
import {
REFERENCE_FEATURE_ID,
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
} from "../../../src/features/reference-feature/contracts/reference-feature-contract.ts";
type Runtime = Parameters<typeof createRuntimeAdapters>[0]["runtime"];
type Release = Parameters<typeof createRuntimeAdapters>[0]["release"];
const runtime: Runtime = {
config: {
APP_ENV: "local",
API_BASE_URL: "http://localhost:8080",
TELEMETRY_ENABLED: false,
AUTH_MODE: "demo",
REQUEST_TIMEOUT_MS: 4321,
MAX_RETRY_ATTEMPTS: 0,
RELEASE_MANIFEST_URL: "/release-manifest.json",
CONFIG_SCHEMA_VERSION: "2.0",
CAPABILITY_OVERRIDES: {
REALTIME: "DEFAULT",
WEB_WORKER: "DEFAULT",
SERVICE_WORKER: "DEFAULT",
OFFLINE_COMMANDS: "DEFAULT",
},
},
configSchema: "V2",
build: {
buildId: "build-a",
commitSha: "abc123",
routerBasePath: "/",
runtimeConfigUrl: "/config.json",
},
validationDurationMs: 0,
};
const release: Release = {
schemaVersion: 2,
appVersion: "0.1.0",
buildId: "build-a",
commitSha: "abc123",
configSchemaVersion: "2.0",
contractSet: {
setAlgorithm: "CA_CONTRACT_SET_V1",
setDigest: `sha256:${"0".repeat(64)}`,
packages: [],
},
assetManifestHash: "hash-a",
releaseId: "release-a",
builtAt: "2026-07-25T00:00:00Z",
routeChunks: { "route-home": "assets/home.js" },
};
function referenceBoundQueryKey() {
const scope: CacheScopeSnapshot = {
generation: 1,
fingerprint: "runtime-scope-fingerprint-0001",
identities: createRuntimeIdentityRegistry({
tokenFactory: () => "runtime-identity-token-0001",
}),
signal: new AbortController().signal,
isCurrent: () => true,
};
return bindQuery(
{
definitionId: "reference-resource-runtime-test-v1",
definitionVersion: 1,
owner: REFERENCE_FEATURE_ID,
namespace: "reference-resource",
namespaceVersion: 1,
operationId: "GET_REFERENCE_RESOURCE",
profileId: "DETAIL_STANDARD",
measureResult: () => ({ itemCount: 1, estimatedBytes: 8 }),
execute: async () => ({ ok: true as const, value: "reference-1" }),
},
"reference-1",
scope,
).queryKey;
}
describe("reference feature runtime composition", () => {
it("invalidates a real bound query through the installed production graph", async () => {
const adapters = await createRuntimeAdapters({ runtime, release, host: {} });
const queryKey = referenceBoundQueryKey();
adapters.infrastructure.queryClient.setQueryData(queryKey, {
resourceId: "reference-1",
});
await adapters.infrastructure.queryInvalidation.invalidate([
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
]);
expect(
adapters.infrastructure.queryClient.getQueryState(queryKey)?.isInvalidated,
).toBe(true);
adapters.infrastructure.dispose();
});
it("executes installed feature HTTP through the composed contract registry", async () => {
const fetcher = vi.fn(async () =>
Response.json([{ id: "reference-1", name: "Direct contract payload" }]),
);
const adapters = await createRuntimeAdapters({
runtime,
release,
host: {},
fetcher,
});
await adapters.outputPorts.session.beginSignIn();
await vi.waitFor(() =>
expect(adapters.infrastructure.serverStateScope.getPhase()).toBe("READY"),
);
await expect(
adapters.featureInputs[REFERENCE_FEATURE_ID].listResources({ limit: 20 }),
).resolves.toEqual({
ok: true,
value: [
{
resourceId: "reference-1",
title: "Direct contract payload",
createdAt: null,
},
],
});
expect(fetcher).toHaveBeenCalledWith(
"http://localhost:8080/api/reference-resources?limit=20",
expect.objectContaining({
method: "GET",
redirect: "error",
cache: "no-store",
}),
);
adapters.infrastructure.dispose();
});
it("keeps private command intent identity out of URLs and diagnostics", async () => {
const requests: Array<Readonly<{ url: string; headers: Headers }>> = [];
const fetcher = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
requests.push({
url: String(input),
headers: new Headers(init?.headers),
});
return Response.json(
{ id: "resource-1", name: "Created resource" },
{ status: 201 },
);
});
const adapters = await createRuntimeAdapters({
runtime,
release,
host: {},
fetcher,
});
await adapters.outputPorts.session.beginSignIn();
await vi.waitFor(() =>
expect(adapters.infrastructure.serverStateScope.getPhase()).toBe("READY"),
);
const intent = Object.freeze({
intentId: "private-intent-id",
operationId: "CREATE_REFERENCE_RESOURCE",
canonicalInputIdentity: "private-canonical-input",
idempotencyKey: "private-idempotency-key",
createdAtMonotonicMs: 42,
});
await expect(
adapters.featureInputs[REFERENCE_FEATURE_ID].createResource(
{ name: "Created resource" },
{ intent },
),
).resolves.toMatchObject({ ok: true });
expect(requests).toHaveLength(1);
expect(requests[0]?.headers.get("Idempotency-Key")).toBe(
"private-idempotency-key",
);
expect(requests[0]?.url).toBe(
"http://localhost:8080/api/reference-resources",
);
const safeEvidence = JSON.stringify({
requests: requests.map((request) => request.url),
diagnostics: adapters.outputPorts.diagnostics.entries(),
});
expect(safeEvidence).not.toContain("private-intent-id");
expect(safeEvidence).not.toContain("private-canonical-input");
expect(safeEvidence).not.toContain("private-idempotency-key");
adapters.infrastructure.dispose();
});
});