import { describe, it, expect, vi, beforeEach } from "vitest"; import type { ExecutionContext } from "@nestjs/common"; import { PermissionGuard, Permissions, PERMISSIONS_KEY, } from "./permission.guard.js"; import type { AuthenticatedRequest } from "./auth.middleware.js"; import { PermissionDeniedError } from "../shared/errors/application-error.js"; describe("PermissionGuard", () => { let guard: PermissionGuard; let reflector: { getAllAndOverride: ReturnType }; function createMockContext( request: Partial, ): ExecutionContext { return { switchToHttp: () => ({ getRequest: () => request as unknown as AuthenticatedRequest, }), getHandler: () => ({}), getClass: () => ({}), } as unknown as ExecutionContext; } beforeEach(() => { reflector = { getAllAndOverride: vi.fn(), }; guard = new PermissionGuard(reflector as never); }); it("should allow access when DEV_MODE is true", () => { const original = process.env.DEV_MODE; process.env.DEV_MODE = "true"; const ctx = createMockContext({ userRoles: [] }); expect(guard.canActivate(ctx)).toBe(true); process.env.DEV_MODE = original; }); it("should allow access when no required permissions", () => { process.env.DEV_MODE = "false"; reflector.getAllAndOverride.mockReturnValue(undefined); const ctx = createMockContext({ userRoles: [] }); expect(guard.canActivate(ctx)).toBe(true); }); it("should allow access when no required permissions (empty array)", () => { reflector.getAllAndOverride.mockReturnValue([]); const ctx = createMockContext({ userRoles: [] }); expect(guard.canActivate(ctx)).toBe(true); }); it("should allow access when user role has required permission", () => { reflector.getAllAndOverride.mockReturnValue([ Permissions.CONTENT_TEXTBOOK_READ, ]); const ctx = createMockContext({ userRoles: ["admin"], }); expect(guard.canActivate(ctx)).toBe(true); }); it("should allow access when teacher role has matching permission", () => { reflector.getAllAndOverride.mockReturnValue([ Permissions.CONTENT_CHAPTER_CREATE, ]); const ctx = createMockContext({ userRoles: ["teacher"], }); expect(guard.canActivate(ctx)).toBe(true); }); it("should allow access when at least one role matches", () => { reflector.getAllAndOverride.mockReturnValue([ Permissions.CONTENT_TEXTBOOK_READ, ]); const ctx = createMockContext({ userRoles: ["student", "unknown-role"], }); expect(guard.canActivate(ctx)).toBe(true); }); it("should throw PermissionDeniedError when user lacks permission", () => { reflector.getAllAndOverride.mockReturnValue([ Permissions.CONTENT_TEXTBOOK_DELETE, ]); const ctx = createMockContext({ userRoles: ["student"], }); expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError); }); it("should throw PermissionDeniedError when user has no roles", () => { reflector.getAllAndOverride.mockReturnValue([ Permissions.CONTENT_TEXTBOOK_READ, ]); const ctx = createMockContext({ userRoles: [], }); expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError); }); it("should use empty roles array when userRoles is undefined", () => { reflector.getAllAndOverride.mockReturnValue([ Permissions.CONTENT_TEXTBOOK_READ, ]); const ctx = createMockContext({}); expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError); }); it("should expose PERMISSIONS_KEY constant", () => { expect(PERMISSIONS_KEY).toBe("permissions"); }); });