aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server/src/routes/auth.test.ts
diff options
context:
space:
mode:
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",