feat(features): contract suites for all repository interfaces

Each repository interface now has a contract suite under
src/__contracts__/. Both Mock and Payload implementations run the
same suite, eliminating mock-vs-real drift. Payload impls back the
contract with an in-memory stub via vi.mock('payload') + a small
buildPayloadStub helper.

Spec: §5.2, §6.4
This commit is contained in:
2026-05-05 15:28:38 +02:00
parent a74f217703
commit e1355e6bc7
17 changed files with 751 additions and 62 deletions

View File

@@ -0,0 +1,47 @@
import { it, expect, beforeEach } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract";
import type { IHeaderRepository } from "../application/repositories/header-repository.interface.js";
/**
* Contract for IHeaderRepository.
*
* Header is a singleton (Payload Global). The interface exposes only
* getHeader(). The contract verifies the shape of the return value.
*/
export const headerRepositoryContract =
defineContractSuite<IHeaderRepository>(
"IHeaderRepository",
({ buildSubject }) => {
let repo: IHeaderRepository;
beforeEach(async () => {
repo = await buildSubject();
});
// --- getHeader ---
it("getHeader returns an object with an items array", async () => {
const header = await repo.getHeader();
expect(header).toBeDefined();
expect(header.items).toBeInstanceOf(Array);
});
it("getHeader items have label, href, and external fields", async () => {
const header = await repo.getHeader();
for (const item of header.items) {
expect(typeof item.label).toBe("string");
expect(item.label.length).toBeGreaterThan(0);
expect(typeof item.href).toBe("string");
expect(item.href.length).toBeGreaterThan(0);
expect(typeof item.external).toBe("boolean");
}
});
it("getHeader logoId is string or undefined", async () => {
const header = await repo.getHeader();
expect(
header.logoId === undefined || typeof header.logoId === "string",
).toBe(true);
});
},
);