import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { ExecutionContext, ForbiddenException } from "@nestjs/common"; import { Reflector } from "@nestjs/core"; import { PermissionGuard } from "./permission.guard"; import { PrismaService } from "../../prisma/prisma.service"; import { Permission } from "../decorators/permissions.decorator"; import { WorkspaceMemberRole } from "@prisma/client"; describe("PermissionGuard", () => { let guard: PermissionGuard; let reflector: Reflector; let prismaService: PrismaService; const mockReflector = { getAllAndOverride: vi.fn(), }; const mockPrismaService = { workspaceMember: { findUnique: vi.fn(), }, }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ PermissionGuard, { provide: Reflector, useValue: mockReflector, }, { provide: PrismaService, useValue: mockPrismaService, }, ], }).compile(); guard = module.get(PermissionGuard); reflector = module.get(Reflector); prismaService = module.get(PrismaService); vi.clearAllMocks(); }); const createMockExecutionContext = (user: any, workspace: any): ExecutionContext => { const mockRequest = { user, workspace, }; return { switchToHttp: () => ({ getRequest: () => mockRequest, }), getHandler: vi.fn(), getClass: vi.fn(), } as any; }; describe("canActivate", () => { const userId = "user-123"; const workspaceId = "workspace-456"; it("should allow access when no permission is required", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(undefined); const result = await guard.canActivate(context); expect(result).toBe(true); }); it("should allow OWNER to access WORKSPACE_OWNER permission", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_OWNER); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.OWNER, }); const result = await guard.canActivate(context); expect(result).toBe(true); expect(context.switchToHttp().getRequest().user.workspaceRole).toBe( WorkspaceMemberRole.OWNER ); }); it("should deny ADMIN access to WORKSPACE_OWNER permission", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_OWNER); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.ADMIN, }); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); }); it("should allow OWNER and ADMIN to access WORKSPACE_ADMIN permission", async () => { const context1 = createMockExecutionContext({ id: userId }, { id: workspaceId }); const context2 = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_ADMIN); // Test OWNER mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.OWNER, }); expect(await guard.canActivate(context1)).toBe(true); // Test ADMIN mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.ADMIN, }); expect(await guard.canActivate(context2)).toBe(true); }); it("should deny MEMBER access to WORKSPACE_ADMIN permission", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_ADMIN); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.MEMBER, }); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); }); it("should allow OWNER, ADMIN, and MEMBER to access WORKSPACE_MEMBER permission", async () => { const context1 = createMockExecutionContext({ id: userId }, { id: workspaceId }); const context2 = createMockExecutionContext({ id: userId }, { id: workspaceId }); const context3 = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_MEMBER); // Test OWNER mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.OWNER, }); expect(await guard.canActivate(context1)).toBe(true); // Test ADMIN mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.ADMIN, }); expect(await guard.canActivate(context2)).toBe(true); // Test MEMBER mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.MEMBER, }); expect(await guard.canActivate(context3)).toBe(true); }); it("should deny GUEST access to WORKSPACE_MEMBER permission", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_MEMBER); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.GUEST, }); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); }); it("should allow any role (including GUEST) to access WORKSPACE_ANY permission", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_ANY); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ role: WorkspaceMemberRole.GUEST, }); const result = await guard.canActivate(context); expect(result).toBe(true); }); it("should throw ForbiddenException when user context is missing", async () => { const context = createMockExecutionContext(null, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_MEMBER); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); }); it("should throw ForbiddenException when workspace context is missing", async () => { const context = createMockExecutionContext({ id: userId }, null); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_MEMBER); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); }); it("should throw ForbiddenException when user is not a workspace member", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_MEMBER); mockPrismaService.workspaceMember.findUnique.mockResolvedValue(null); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); await expect(guard.canActivate(context)).rejects.toThrow( "You are not a member of this workspace" ); }); it("should handle database errors gracefully", async () => { const context = createMockExecutionContext({ id: userId }, { id: workspaceId }); mockReflector.getAllAndOverride.mockReturnValue(Permission.WORKSPACE_MEMBER); mockPrismaService.workspaceMember.findUnique.mockRejectedValue(new Error("Database error")); await expect(guard.canActivate(context)).rejects.toThrow(ForbiddenException); }); }); });