123 lines
3.7 KiB
TypeScript
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 },
|
|
});
|
|
});
|
|
});
|