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,8 @@
import { describe } from "vitest";
import { MockUsersRepository } from "@/infrastructure/repositories/mock-users.repository";
import { usersRepositoryContract } from "@/__contracts__/users-repository.contract";
describe("MockUsersRepository", () => {
// Start with empty store so contract tests run from a clean slate.
usersRepositoryContract.run(() => new MockUsersRepository([]));
});

View File

@@ -4,12 +4,18 @@ import { injectable } from "inversify";
import type { IUsersRepository } from "../../application/repositories/users-repository.interface";
import type { User } from "../../entities/user";
const DEFAULT_SEED: User[] = [
{ id: "1", username: "alice", passwordHash: "hashed_password_alice" },
{ id: "2", username: "bob", passwordHash: "hashed_password_bob" },
];
@injectable()
export class MockUsersRepository implements IUsersRepository {
private _users: User[] = [
{ id: "1", username: "alice", passwordHash: "hashed_password_alice" },
{ id: "2", username: "bob", passwordHash: "hashed_password_bob" },
];
private _users: User[];
constructor(initialUsers: User[] = DEFAULT_SEED) {
this._users = [...initialUsers];
}
async getUser(id: string): Promise<User | undefined> {
return this._users.find((u) => u.id === id);