import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { ExecutionContext, UnauthorizedException } from "@nestjs/common"; import { AuthGuard } from "./auth.guard"; import { AuthService } from "../auth.service"; describe("AuthGuard", () => { let guard: AuthGuard; let authService: AuthService; const mockAuthService = { verifySession: vi.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ AuthGuard, { provide: AuthService, useValue: mockAuthService, }, ], }).compile(); guard = module.get(AuthGuard); authService = module.get(AuthService); vi.clearAllMocks(); }); const createMockExecutionContext = ( headers: Record = {}, cookies: Record = {} ): ExecutionContext => { const mockRequest = { headers, cookies, }; return { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; }; describe("canActivate", () => { const mockSessionData = { user: { id: "user-123", email: "test@example.com", name: "Test User", }, session: { id: "session-123", token: "session-token", expiresAt: new Date(Date.now() + 86400000), }, }; describe("Bearer token authentication", () => { it("should return true for valid Bearer token", async () => { mockAuthService.verifySession.mockResolvedValue(mockSessionData); const context = createMockExecutionContext({ authorization: "Bearer valid-token", }); const result = await guard.canActivate(context); expect(result).toBe(true); expect(mockAuthService.verifySession).toHaveBeenCalledWith("valid-token"); }); it("should throw UnauthorizedException for invalid Bearer token", async () => { mockAuthService.verifySession.mockResolvedValue(null); const context = createMockExecutionContext({ authorization: "Bearer invalid-token", }); await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); await expect(guard.canActivate(context)).rejects.toThrow("Invalid or expired session"); }); }); describe("Cookie-based authentication", () => { it("should return true for valid session cookie", async () => { mockAuthService.verifySession.mockResolvedValue(mockSessionData); const context = createMockExecutionContext( {}, { "better-auth.session_token": "cookie-token", } ); const result = await guard.canActivate(context); expect(result).toBe(true); expect(mockAuthService.verifySession).toHaveBeenCalledWith("cookie-token"); }); it("should prefer cookie over Bearer token when both present", async () => { mockAuthService.verifySession.mockResolvedValue(mockSessionData); const context = createMockExecutionContext( { authorization: "Bearer bearer-token", }, { "better-auth.session_token": "cookie-token", } ); const result = await guard.canActivate(context); expect(result).toBe(true); expect(mockAuthService.verifySession).toHaveBeenCalledWith("cookie-token"); }); it("should fallback to Bearer token if no cookie", async () => { mockAuthService.verifySession.mockResolvedValue(mockSessionData); const context = createMockExecutionContext( { authorization: "Bearer bearer-token", }, {} ); const result = await guard.canActivate(context); expect(result).toBe(true); expect(mockAuthService.verifySession).toHaveBeenCalledWith("bearer-token"); }); }); describe("Error handling", () => { it("should throw UnauthorizedException if no token provided", async () => { const context = createMockExecutionContext({}, {}); await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); await expect(guard.canActivate(context)).rejects.toThrow( "No authentication token provided" ); }); it("should throw UnauthorizedException if session verification fails", async () => { mockAuthService.verifySession.mockRejectedValue(new Error("Verification failed")); const context = createMockExecutionContext({ authorization: "Bearer error-token", }); await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException); await expect(guard.canActivate(context)).rejects.toThrow("Authentication failed"); }); it("should attach user and session to request on success", async () => { mockAuthService.verifySession.mockResolvedValue(mockSessionData); const mockRequest = { headers: { authorization: "Bearer valid-token", }, cookies: {}, }; const context = { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; await guard.canActivate(context); expect(mockRequest).toHaveProperty("user", mockSessionData.user); expect(mockRequest).toHaveProperty("session", mockSessionData.session); }); }); }); });