feat(core): add auth controllers with tests (sign-in, sign-up, sign-out)

This commit is contained in:
2026-04-06 14:28:57 +02:00
parent f1041f2f1d
commit b920da1e8f
6 changed files with 189 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import { z } from "zod";
import { InputParseError } from "@/entities/errors/common.js";
import type { Cookie } from "@/entities/models/cookie.js";
import { signInUseCase } from "@/application/use-cases/auth/sign-in.use-case.js";
const inputSchema = z.object({
username: z.string().min(3).max(31),
password: z.string().min(6).max(255),
});
export async function signInController(
input: Partial<z.infer<typeof inputSchema>>
): Promise<Cookie> {
const { data, error: inputParseError } = inputSchema.safeParse(input);
if (inputParseError) {
throw new InputParseError("Invalid data", { cause: inputParseError });
}
const { cookie } = await signInUseCase(data);
return cookie;
}

View File

@@ -0,0 +1,14 @@
import { InputParseError } from "@/entities/errors/common.js";
import type { Cookie } from "@/entities/models/cookie.js";
import { signOutUseCase } from "@/application/use-cases/auth/sign-out.use-case.js";
export async function signOutController(
sessionId: string | undefined
): Promise<Cookie> {
if (!sessionId) {
throw new InputParseError("Must provide a session ID");
}
const { blankCookie } = await signOutUseCase(sessionId);
return blankCookie;
}

View File

@@ -0,0 +1,37 @@
import { z } from "zod";
import { InputParseError } from "@/entities/errors/common.js";
import { signUpUseCase } from "@/application/use-cases/auth/sign-up.use-case.js";
const inputSchema = z
.object({
username: z.string().min(3).max(31),
password: z.string().min(6).max(255),
confirmPassword: z.string().min(6).max(255),
})
.superRefine(({ password, confirmPassword }, ctx) => {
if (confirmPassword !== password) {
ctx.addIssue({
code: "custom",
message: "The passwords did not match",
path: ["password"],
});
ctx.addIssue({
code: "custom",
message: "The passwords did not match",
path: ["confirmPassword"],
});
}
});
export async function signUpController(
input: Partial<z.infer<typeof inputSchema>>
): Promise<ReturnType<typeof signUpUseCase>> {
const { data, error: inputParseError } = inputSchema.safeParse(input);
if (inputParseError) {
throw new InputParseError("Invalid data", { cause: inputParseError });
}
return await signUpUseCase(data);
}

View File

@@ -0,0 +1,41 @@
import "reflect-metadata";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
destroyContainer,
initializeContainer,
} from "@/di/container.js";
import { signInController } from "@/interface-adapters/controllers/auth/sign-in.controller.js";
import { InputParseError } from "@/entities/errors/common.js";
import { AuthenticationError } from "@/entities/errors/auth.js";
beforeEach(() => {
initializeContainer();
});
afterEach(() => {
destroyContainer();
});
describe("signInController", () => {
it("returns cookie for valid input", async () => {
const cookie = await signInController({
username: "alice",
password: "password_alice",
});
expect(cookie).toHaveProperty("name");
expect(cookie).toHaveProperty("value");
});
it("throws InputParseError for invalid input", async () => {
await expect(
signInController({ username: "ab", password: "short" })
).rejects.toBeInstanceOf(InputParseError);
});
it("throws AuthenticationError for wrong credentials", async () => {
await expect(
signInController({ username: "alice", password: "wrongpassword" })
).rejects.toBeInstanceOf(AuthenticationError);
});
});

View File

@@ -0,0 +1,30 @@
import "reflect-metadata";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
destroyContainer,
initializeContainer,
} from "@/di/container.js";
import { signOutController } from "@/interface-adapters/controllers/auth/sign-out.controller.js";
import { InputParseError } from "@/entities/errors/common.js";
beforeEach(() => {
initializeContainer();
});
afterEach(() => {
destroyContainer();
});
describe("signOutController", () => {
it("returns blank cookie for valid session", async () => {
const cookie = await signOutController("some-session-id");
expect(cookie.value).toBe("");
});
it("throws InputParseError when no session ID provided", async () => {
await expect(signOutController(undefined)).rejects.toBeInstanceOf(
InputParseError
);
});
});

View File

@@ -0,0 +1,44 @@
import "reflect-metadata";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
destroyContainer,
initializeContainer,
} from "@/di/container.js";
import { signUpController } from "@/interface-adapters/controllers/auth/sign-up.controller.js";
import { InputParseError } from "@/entities/errors/common.js";
beforeEach(() => {
initializeContainer();
});
afterEach(() => {
destroyContainer();
});
describe("signUpController", () => {
it("returns session, cookie, and user for valid input", async () => {
const result = await signUpController({
username: "newuser",
password: "securepassword",
confirmPassword: "securepassword",
});
expect(result).toHaveProperty("session");
expect(result).toHaveProperty("cookie");
expect(result).toHaveProperty("user");
});
it("throws InputParseError when passwords don't match", async () => {
await expect(
signUpController({
username: "newuser",
password: "password1",
confirmPassword: "password2",
})
).rejects.toBeInstanceOf(InputParseError);
});
it("throws InputParseError for missing fields", async () => {
await expect(signUpController({})).rejects.toBeInstanceOf(InputParseError);
});
});