aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server/src/routes/auth.test.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-03 06:05:33 +0900
committernsfisis <nsfisis@gmail.com>2025-12-04 23:26:34 +0900
commitdbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3 (patch)
tree5780b2d0b0f32735477ff8f1f3308720d059ce67 /pkgs/server/src/routes/auth.test.ts
parent20f159abf06599bc8902a445be21d4c085d82ede (diff)
downloadkioku-dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3.tar.gz
kioku-dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3.tar.zst
kioku-dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3.zip
refactor(auth): introduce repository pattern for database access
Add repository types and implementations to abstract database operations, improving testability and separation of concerns. The auth routes now use dependency injection with UserRepository and RefreshTokenRepository interfaces, making tests simpler by mocking interfaces instead of Drizzle query builders. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'pkgs/server/src/routes/auth.test.ts')
-rw-r--r--pkgs/server/src/routes/auth.test.ts293
1 files changed, 104 insertions, 189 deletions
diff --git a/pkgs/server/src/routes/auth.test.ts b/pkgs/server/src/routes/auth.test.ts
index 7de04f5..34eb2b6 100644
--- a/pkgs/server/src/routes/auth.test.ts
+++ b/pkgs/server/src/routes/auth.test.ts
@@ -1,60 +1,12 @@
import { Hono } from "hono";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { errorHandler } from "../middleware";
-import { auth } from "./auth";
-
-vi.mock("../db", () => {
- const mockUsers: Array<{
- id: string;
- username: string;
- passwordHash: string;
- createdAt: Date;
- }> = [];
-
- return {
- db: {
- select: vi.fn(() => ({
- from: vi.fn(() => ({
- where: vi.fn(() => ({
- limit: vi.fn(() =>
- Promise.resolve(
- mockUsers.filter((u) => u.username === "existinguser"),
- ),
- ),
- })),
- })),
- })),
- insert: vi.fn(() => ({
- values: vi.fn((data: { username: string; passwordHash: string }) => ({
- returning: vi.fn(() => {
- const newUser = {
- id: "test-uuid-123",
- username: data.username,
- createdAt: new Date("2024-01-01T00:00:00Z"),
- };
- mockUsers.push({ ...newUser, passwordHash: data.passwordHash });
- return Promise.resolve([newUser]);
- }),
- })),
- })),
- delete: vi.fn(() => ({
- where: vi.fn(() => Promise.resolve(undefined)),
- })),
- },
- users: {
- id: "id",
- username: "username",
- createdAt: "created_at",
- },
- refreshTokens: {
- id: "id",
- userId: "user_id",
- tokenHash: "token_hash",
- expiresAt: "expires_at",
- createdAt: "created_at",
- },
- };
-});
+import type {
+ RefreshTokenRepository,
+ UserPublic,
+ UserRepository,
+} from "../repositories";
+import { createAuthRouter } from "./auth";
vi.mock("argon2", () => ({
hash: vi.fn((password: string) => Promise.resolve(`hashed_${password}`)),
@@ -63,6 +15,23 @@ vi.mock("argon2", () => ({
),
}));
+function createMockUserRepo(): UserRepository {
+ return {
+ findByUsername: vi.fn(),
+ existsByUsername: vi.fn(),
+ create: vi.fn(),
+ findById: vi.fn(),
+ };
+}
+
+function createMockRefreshTokenRepo(): RefreshTokenRepository {
+ return {
+ findValidToken: vi.fn(),
+ create: vi.fn(),
+ deleteById: vi.fn(),
+ };
+}
+
interface RegisterResponse {
user?: {
id: string;
@@ -90,15 +59,30 @@ interface LoginResponse {
describe("POST /register", () => {
let app: Hono;
+ let mockUserRepo: ReturnType<typeof createMockUserRepo>;
+ let mockRefreshTokenRepo: ReturnType<typeof createMockRefreshTokenRepo>;
beforeEach(() => {
vi.clearAllMocks();
+ mockUserRepo = createMockUserRepo();
+ mockRefreshTokenRepo = createMockRefreshTokenRepo();
+ const auth = createAuthRouter({
+ userRepo: mockUserRepo,
+ refreshTokenRepo: mockRefreshTokenRepo,
+ });
app = new Hono();
app.onError(errorHandler);
app.route("/api/auth", auth);
});
it("creates a new user with valid credentials", async () => {
+ vi.mocked(mockUserRepo.existsByUsername).mockResolvedValue(false);
+ vi.mocked(mockUserRepo.create).mockResolvedValue({
+ id: "test-uuid-123",
+ username: "testuser",
+ createdAt: new Date("2024-01-01T00:00:00Z"),
+ } as UserPublic);
+
const res = await app.request("/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -115,6 +99,11 @@ describe("POST /register", () => {
username: "testuser",
createdAt: "2024-01-01T00:00:00.000Z",
});
+ expect(mockUserRepo.existsByUsername).toHaveBeenCalledWith("testuser");
+ expect(mockUserRepo.create).toHaveBeenCalledWith({
+ username: "testuser",
+ passwordHash: "hashed_securepassword12345",
+ });
});
it("returns 422 for invalid username", async () => {
@@ -148,15 +137,7 @@ describe("POST /register", () => {
});
it("returns 409 for existing username", async () => {
- const { db } = await import("../db");
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([{ id: "existing-id" }]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
+ vi.mocked(mockUserRepo.existsByUsername).mockResolvedValue(true);
const res = await app.request("/api/auth/register", {
method: "POST",
@@ -175,34 +156,29 @@ describe("POST /register", () => {
describe("POST /login", () => {
let app: Hono;
+ let mockUserRepo: ReturnType<typeof createMockUserRepo>;
+ let mockRefreshTokenRepo: ReturnType<typeof createMockRefreshTokenRepo>;
beforeEach(() => {
vi.clearAllMocks();
+ mockUserRepo = createMockUserRepo();
+ mockRefreshTokenRepo = createMockRefreshTokenRepo();
+ const auth = createAuthRouter({
+ userRepo: mockUserRepo,
+ refreshTokenRepo: mockRefreshTokenRepo,
+ });
app = new Hono();
app.onError(errorHandler);
app.route("/api/auth", auth);
});
it("returns access token for valid credentials", async () => {
- const { db } = await import("../db");
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([
- {
- id: "user-uuid-123",
- username: "testuser",
- passwordHash: "hashed_correctpassword",
- },
- ]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
-
- // Mock the insert call for refresh token
- vi.mocked(db.insert).mockReturnValueOnce({
- values: vi.fn().mockResolvedValue(undefined),
- } as unknown as ReturnType<typeof db.insert>);
+ vi.mocked(mockUserRepo.findByUsername).mockResolvedValue({
+ id: "user-uuid-123",
+ username: "testuser",
+ passwordHash: "hashed_correctpassword",
+ });
+ vi.mocked(mockRefreshTokenRepo.create).mockResolvedValue(undefined);
const res = await app.request("/api/auth/login", {
method: "POST",
@@ -223,17 +199,15 @@ describe("POST /login", () => {
id: "user-uuid-123",
username: "testuser",
});
+ expect(mockRefreshTokenRepo.create).toHaveBeenCalledWith({
+ userId: "user-uuid-123",
+ tokenHash: expect.any(String),
+ expiresAt: expect.any(Date),
+ });
});
it("returns 401 for non-existent user", async () => {
- const { db } = await import("../db");
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
+ vi.mocked(mockUserRepo.findByUsername).mockResolvedValue(undefined);
const res = await app.request("/api/auth/login", {
method: "POST",
@@ -250,20 +224,11 @@ describe("POST /login", () => {
});
it("returns 401 for incorrect password", async () => {
- const { db } = await import("../db");
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([
- {
- id: "user-uuid-123",
- username: "testuser",
- passwordHash: "hashed_correctpassword",
- },
- ]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
+ vi.mocked(mockUserRepo.findByUsername).mockResolvedValue({
+ id: "user-uuid-123",
+ username: "testuser",
+ passwordHash: "hashed_correctpassword",
+ });
const res = await app.request("/api/auth/login", {
method: "POST",
@@ -325,55 +290,34 @@ interface RefreshResponse {
describe("POST /refresh", () => {
let app: Hono;
+ let mockUserRepo: ReturnType<typeof createMockUserRepo>;
+ let mockRefreshTokenRepo: ReturnType<typeof createMockRefreshTokenRepo>;
beforeEach(() => {
vi.clearAllMocks();
+ mockUserRepo = createMockUserRepo();
+ mockRefreshTokenRepo = createMockRefreshTokenRepo();
+ const auth = createAuthRouter({
+ userRepo: mockUserRepo,
+ refreshTokenRepo: mockRefreshTokenRepo,
+ });
app = new Hono();
app.onError(errorHandler);
app.route("/api/auth", auth);
});
it("returns new tokens for valid refresh token", async () => {
- const { db } = await import("../db");
-
- // Mock finding valid refresh token
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([
- {
- id: "token-id-123",
- userId: "user-uuid-123",
- expiresAt: new Date(Date.now() + 86400000), // expires in 1 day
- },
- ]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
-
- // Mock finding user
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([
- {
- id: "user-uuid-123",
- username: "testuser",
- },
- ]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
-
- // Mock delete old token
- vi.mocked(db.delete).mockReturnValueOnce({
- where: vi.fn().mockResolvedValue(undefined),
- } as unknown as ReturnType<typeof db.delete>);
-
- // Mock insert new token
- vi.mocked(db.insert).mockReturnValueOnce({
- values: vi.fn().mockResolvedValue(undefined),
- } as unknown as ReturnType<typeof db.insert>);
+ vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue({
+ id: "token-id-123",
+ userId: "user-uuid-123",
+ expiresAt: new Date(Date.now() + 86400000),
+ });
+ vi.mocked(mockUserRepo.findById).mockResolvedValue({
+ id: "user-uuid-123",
+ username: "testuser",
+ });
+ vi.mocked(mockRefreshTokenRepo.deleteById).mockResolvedValue(undefined);
+ vi.mocked(mockRefreshTokenRepo.create).mockResolvedValue(undefined);
const res = await app.request("/api/auth/refresh", {
method: "POST",
@@ -393,19 +337,18 @@ describe("POST /refresh", () => {
id: "user-uuid-123",
username: "testuser",
});
+ expect(mockRefreshTokenRepo.deleteById).toHaveBeenCalledWith(
+ "token-id-123",
+ );
+ expect(mockRefreshTokenRepo.create).toHaveBeenCalledWith({
+ userId: "user-uuid-123",
+ tokenHash: expect.any(String),
+ expiresAt: expect.any(Date),
+ });
});
it("returns 401 for invalid refresh token", async () => {
- const { db } = await import("../db");
-
- // Mock no token found
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
+ vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue(undefined);
const res = await app.request("/api/auth/refresh", {
method: "POST",
@@ -421,16 +364,7 @@ describe("POST /refresh", () => {
});
it("returns 401 for expired refresh token", async () => {
- const { db } = await import("../db");
-
- // Mock no valid (non-expired) token found (empty result because expiry check in query)
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
+ vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue(undefined);
const res = await app.request("/api/auth/refresh", {
method: "POST",
@@ -446,31 +380,12 @@ describe("POST /refresh", () => {
});
it("returns 401 when user not found", async () => {
- const { db } = await import("../db");
-
- // Mock finding valid refresh token
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([
- {
- id: "token-id-123",
- userId: "deleted-user-id",
- expiresAt: new Date(Date.now() + 86400000),
- },
- ]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
-
- // Mock user not found
- vi.mocked(db.select).mockReturnValueOnce({
- from: vi.fn().mockReturnValue({
- where: vi.fn().mockReturnValue({
- limit: vi.fn().mockResolvedValue([]),
- }),
- }),
- } as unknown as ReturnType<typeof db.select>);
+ vi.mocked(mockRefreshTokenRepo.findValidToken).mockResolvedValue({
+ id: "token-id-123",
+ userId: "deleted-user-id",
+ expiresAt: new Date(Date.now() + 86400000),
+ });
+ vi.mocked(mockUserRepo.findById).mockResolvedValue(undefined);
const res = await app.request("/api/auth/refresh", {
method: "POST",