A personal media tracker built on the AT Protocol opnshelf.xyz
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 206 lines 5.9 kB view raw
1import { type ExecutionContext, UnauthorizedException } from "@nestjs/common"; 2import { Test, type TestingModule } from "@nestjs/testing"; 3 4// Mock PrismaService before importing AuthService/AuthGuard 5jest.mock("../prisma/prisma.service", () => ({ 6 PrismaService: jest.fn(), 7})); 8 9// Mock @atproto modules to prevent import errors 10jest.mock("@atproto/oauth-client-node", () => ({})); 11jest.mock("@atproto/api", () => ({})); 12 13import { AuthGuard } from "./auth.guard"; 14import { AuthService } from "./auth.service"; 15 16describe("AuthGuard", () => { 17 let guard: AuthGuard; 18 let authService: jest.Mocked<AuthService>; 19 20 const mockAuthService = { 21 getSessionById: jest.fn(), 22 restore: jest.fn(), 23 }; 24 25 const createMockExecutionContext = ( 26 cookies: Record<string, string> = {}, 27 headers: Record<string, string> = {}, 28 ) => { 29 const mockRequest = { 30 cookies, 31 headers, 32 }; 33 34 return { 35 switchToHttp: () => ({ 36 getRequest: () => mockRequest, 37 }), 38 } as unknown as ExecutionContext; 39 }; 40 41 beforeEach(async () => { 42 jest.clearAllMocks(); 43 44 const module: TestingModule = await Test.createTestingModule({ 45 providers: [ 46 AuthGuard, 47 { provide: AuthService, useValue: mockAuthService }, 48 ], 49 }).compile(); 50 51 guard = module.get<AuthGuard>(AuthGuard); 52 authService = module.get(AuthService); 53 }); 54 55 describe("canActivate", () => { 56 it("should throw UnauthorizedException when no session cookie", async () => { 57 const context = createMockExecutionContext({}); 58 59 await expect(guard.canActivate(context)).rejects.toThrow( 60 new UnauthorizedException("Not authenticated"), 61 ); 62 }); 63 64 it("should throw UnauthorizedException when session not found in DB", async () => { 65 mockAuthService.getSessionById.mockResolvedValue(null); 66 const context = createMockExecutionContext({ 67 session: "invalid-session", 68 }); 69 70 await expect(guard.canActivate(context)).rejects.toThrow( 71 new UnauthorizedException("Session not found or expired"), 72 ); 73 expect(mockAuthService.getSessionById).toHaveBeenCalledWith( 74 "invalid-session", 75 ); 76 }); 77 78 it("should throw UnauthorizedException when restore returns null", async () => { 79 const mockSessionRecord = { 80 id: "session-123", 81 userDid: "did:plc:abc123", 82 sessionData: "{}", 83 createdAt: new Date(), 84 updatedAt: new Date(), 85 }; 86 mockAuthService.getSessionById.mockResolvedValue(mockSessionRecord); 87 mockAuthService.restore.mockResolvedValue(undefined); 88 89 const context = createMockExecutionContext({ session: "session-123" }); 90 91 await expect(guard.canActivate(context)).rejects.toThrow( 92 new UnauthorizedException("Session not found or expired"), 93 ); 94 expect(mockAuthService.restore).toHaveBeenCalledWith("did:plc:abc123"); 95 }); 96 97 it("should attach user to request and return true when valid session", async () => { 98 const mockSessionRecord = { 99 id: "session-123", 100 userDid: "did:plc:abc123", 101 sessionData: "{}", 102 createdAt: new Date(), 103 updatedAt: new Date(), 104 }; 105 const mockSession = { did: "did:plc:abc123" }; 106 107 mockAuthService.getSessionById.mockResolvedValue(mockSessionRecord); 108 mockAuthService.restore.mockResolvedValue(mockSession); 109 110 const mockRequest = { cookies: { session: "session-123" }, headers: {} }; 111 const context = { 112 switchToHttp: () => ({ 113 getRequest: () => mockRequest, 114 }), 115 } as unknown as ExecutionContext; 116 117 const result = await guard.canActivate(context); 118 119 expect(result).toBe(true); 120 expect((mockRequest as any).user).toEqual({ 121 did: "did:plc:abc123", 122 session: mockSession, 123 }); 124 }); 125 126 it("should rethrow UnauthorizedException from inner try block", async () => { 127 const mockSessionRecord = { 128 id: "session-123", 129 userDid: "did:plc:abc123", 130 sessionData: "{}", 131 createdAt: new Date(), 132 updatedAt: new Date(), 133 }; 134 mockAuthService.getSessionById.mockResolvedValue(mockSessionRecord); 135 mockAuthService.restore.mockResolvedValue(null); 136 137 const context = createMockExecutionContext({ session: "session-123" }); 138 139 await expect(guard.canActivate(context)).rejects.toThrow( 140 UnauthorizedException, 141 ); 142 }); 143 144 it("should throw generic UnauthorizedException on non-Unauthorized errors", async () => { 145 const mockSessionRecord = { 146 id: "session-123", 147 userDid: "did:plc:abc123", 148 sessionData: "{}", 149 createdAt: new Date(), 150 updatedAt: new Date(), 151 }; 152 mockAuthService.getSessionById.mockResolvedValue(mockSessionRecord); 153 mockAuthService.restore.mockRejectedValue(new Error("Database error")); 154 155 const context = createMockExecutionContext({ session: "session-123" }); 156 157 await expect(guard.canActivate(context)).rejects.toThrow( 158 new UnauthorizedException("Invalid or expired session"), 159 ); 160 }); 161 162 it("should handle undefined cookies object", async () => { 163 const mockRequest = { headers: {} }; 164 const context = { 165 switchToHttp: () => ({ 166 getRequest: () => mockRequest, 167 }), 168 } as unknown as ExecutionContext; 169 170 await expect(guard.canActivate(context)).rejects.toThrow( 171 new UnauthorizedException("Not authenticated"), 172 ); 173 }); 174 175 it("should authenticate with Bearer token", async () => { 176 const mockSessionRecord = { 177 id: "session-123", 178 userDid: "did:plc:abc123", 179 sessionData: "{}", 180 createdAt: new Date(), 181 updatedAt: new Date(), 182 }; 183 const mockSession = { did: "did:plc:abc123" }; 184 185 mockAuthService.getSessionById.mockResolvedValue(mockSessionRecord); 186 mockAuthService.restore.mockResolvedValue(mockSession); 187 188 const mockRequest = { 189 cookies: {}, 190 headers: { authorization: "Bearer session-123" }, 191 }; 192 const context = { 193 switchToHttp: () => ({ 194 getRequest: () => mockRequest, 195 }), 196 } as unknown as ExecutionContext; 197 198 const result = await guard.canActivate(context); 199 200 expect(result).toBe(true); 201 expect(mockAuthService.getSessionById).toHaveBeenCalledWith( 202 "session-123", 203 ); 204 }); 205 }); 206});