feat(auth): add per-feature InversifyJS container with constructor-injected service

This commit is contained in:
2026-05-05 00:40:09 +02:00
parent b8cfe0e1f2
commit 2ad6b21f17
4 changed files with 75 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { authContainer } from "./container";
import { AUTH_SYMBOLS } from "./symbols";
import { AuthModule } from "./module";
import { MockUsersRepository } from "@/infrastructure/repositories/mock-users.repository";
import { MockAuthenticationService } from "@/infrastructure/services/mock-authentication.service";
import type { IUsersRepository } from "@/application/repositories/users-repository.interface";
import type { IAuthenticationService } from "@/application/services/authentication-service.interface";
describe("authContainer", () => {
beforeEach(() => {
authContainer.unbindAll();
authContainer.load(AuthModule);
});
afterEach(() => {
authContainer.unbindAll();
});
it("resolves IUsersRepository to MockUsersRepository by default", () => {
const repo = authContainer.get<IUsersRepository>(
AUTH_SYMBOLS.IUsersRepository,
);
expect(repo).toBeInstanceOf(MockUsersRepository);
});
it("resolves IAuthenticationService to MockAuthenticationService by default", () => {
const service = authContainer.get<IAuthenticationService>(
AUTH_SYMBOLS.IAuthenticationService,
);
expect(service).toBeInstanceOf(MockAuthenticationService);
});
it("authentication service receives users repository via constructor injection", async () => {
const service = authContainer.get<IAuthenticationService>(
AUTH_SYMBOLS.IAuthenticationService,
);
// The service should be able to validate against the seeded users
const { session, cookie } = await service.createSession({
id: "1",
username: "alice",
passwordHash: "hashed_password_alice",
});
expect(session.userId).toBe("1");
expect(cookie.value).toBe(session.id);
// After session creation, validateSession should resolve user via the repo
const validated = await service.validateSession(session.id);
expect(validated.user.username).toBe("alice");
});
});

View File

@@ -0,0 +1,6 @@
import "reflect-metadata";
import { Container } from "inversify";
import { AuthModule } from "./module";
export const authContainer = new Container({ defaultScope: "Singleton" });
authContainer.load(AuthModule);

View File

@@ -0,0 +1,14 @@
import { ContainerModule, type interfaces } from "inversify";
import type { IUsersRepository } from "../application/repositories/users-repository.interface";
import type { IAuthenticationService } from "../application/services/authentication-service.interface";
import { MockUsersRepository } from "../infrastructure/repositories/mock-users.repository";
import { MockAuthenticationService } from "../infrastructure/services/mock-authentication.service";
import { AUTH_SYMBOLS } from "./symbols";
export const AuthModule = new ContainerModule((bind: interfaces.Bind) => {
bind<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository).to(MockUsersRepository);
bind<IAuthenticationService>(AUTH_SYMBOLS.IAuthenticationService).to(
MockAuthenticationService,
);
});

View File

@@ -0,0 +1,4 @@
export const AUTH_SYMBOLS = {
IUsersRepository: Symbol.for("auth:IUsersRepository"),
IAuthenticationService: Symbol.for("auth:IAuthenticationService"),
} as const;