feat(core): add auth controllers with tests (sign-in, sign-up, sign-out)
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user