Files
clean-architecture-frontend…/tests/contract/reusable-capability/feature-adapter-composition.test.ts
T

123 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type {
IndexedDbRepositoryPort,
} from "../../../src/application/ports/browser-file-storage/index.ts";
import type {
InstalledHttpOperationExecutor,
} from "../../../src/adapters/http/index.ts";
import {
composeFeatureAdapterInputs,
defineFeatureAdapterContribution,
type IndexedDbRepositoryProvider,
} from "../../../src/features/feature-adapter-contribution.ts";
import {
LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION,
} from "../../../src/features/local-draft-feature/adapters/create-local-draft-feature-input.ts";
import type { LocalDraft } from "../../../src/features/local-draft-feature/domain/local-draft.ts";
type TestHttpFeatureInput = Readonly<{
ping(): Promise<"pong">;
}>;
declare module "../../../src/application/ports/in/application-api.ts" {
interface ApplicationFeatureInputs {
"test-http-feature": TestHttpFeatureInput;
}
}
const TEST_HTTP_FEATURE_ADAPTER_CONTRIBUTION =
defineFeatureAdapterContribution({
featureId: "test-http-feature",
needs: ["http"] as const,
createInput() {
return Object.freeze({
featureId: "test-http-feature" as const,
input: Object.freeze({
async ping() {
return "pong" as const;
},
}),
});
},
});
function localDraftRepository() {
let stored: Readonly<{ value: LocalDraft; revision: number }> | null = null;
const repository = Object.freeze({
async compareAndSwap(input: Readonly<{ value: LocalDraft }>) {
stored = Object.freeze({ value: input.value, revision: 1 });
return Object.freeze({
ok: true as const,
value: Object.freeze({ revision: 1 }),
});
},
async read() {
return Object.freeze({ ok: true as const, value: stored });
},
async remove() {
stored = null;
return Object.freeze({
ok: true as const,
value: Object.freeze({ revision: 2 }),
});
},
}) as unknown as IndexedDbRepositoryPort<LocalDraft, never>;
return repository;
}
describe("feature adapter composition", () => {
it("composes HTTP-only and IndexedDB-only concrete contributions together", async () => {
const repository = localDraftRepository();
const get = ((repositoryId: string) => {
expect(repositoryId).toBe("local-draft");
return repository;
}) as unknown as IndexedDbRepositoryProvider["get"];
const indexedDb = Object.freeze({ get }) satisfies IndexedDbRepositoryProvider;
const http = Object.freeze({
execute: async () => {
throw new Error("HTTP should not execute during composition");
},
}) as unknown as InstalledHttpOperationExecutor;
const inputs = composeFeatureAdapterInputs(
Object.freeze([
TEST_HTTP_FEATURE_ADAPTER_CONTRIBUTION,
LOCAL_DRAFT_FEATURE_ADAPTER_CONTRIBUTION,
] as const),
Object.freeze(["test-http-feature", "local-draft"]),
Object.freeze({ http, indexedDb }),
);
expect(Object.keys(inputs).sort()).toEqual([
"local-draft",
"test-http-feature",
]);
await expect(inputs["test-http-feature"]?.ping()).resolves.toBe("pong");
const localDraft = inputs["local-draft"];
if (!localDraft) throw new Error("local-draft input was not composed");
const draft = Object.freeze({
draftId: "draft-1",
title: "Local draft",
body: "Body",
});
await expect(
localDraft.saveDraft({
draft,
expectedRevision: null,
idempotencyKey: "save-draft-1",
}),
).resolves.toEqual({ ok: true, value: { revision: 1 } });
await expect(localDraft.findDraft("draft-1")).resolves.toEqual({
ok: true,
value: { draft, revision: 1 },
});
});
});