/** * Workspace Access Integration Tests * * Tests that workspace-scoped federation endpoints enforce workspace access. */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { WorkspaceGuard } from "../common/guards/workspace.guard"; import { PrismaService } from "../prisma/prisma.service"; import { ForbiddenException } from "@nestjs/common"; import type { AuthenticatedRequest } from "../common/types/user.types"; describe("Workspace Access Control - Federation", () => { let prismaService: PrismaService; let workspaceGuard: WorkspaceGuard; beforeEach(() => { const mockPrismaService = { workspaceMember: { findUnique: vi.fn(), }, }; prismaService = mockPrismaService as unknown as PrismaService; workspaceGuard = new WorkspaceGuard(prismaService); }); describe("Workspace membership verification", () => { it("should allow access when user is workspace member", async () => { const mockRequest: Partial = { user: { id: "user-123", email: "test@example.com", workspaceId: "workspace-456", }, headers: { "x-workspace-id": "workspace-456", }, params: {}, body: {}, } as AuthenticatedRequest; // Mock workspace membership exists vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue({ workspaceId: "workspace-456", userId: "user-123", role: "ADMIN", createdAt: new Date(), updatedAt: new Date(), } as any); const canActivate = await workspaceGuard.canActivate({ switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any); expect(canActivate).toBe(true); }); it("should deny access when user is not workspace member", async () => { const mockRequest: Partial = { user: { id: "user-123", email: "test@example.com", }, headers: { "x-workspace-id": "workspace-999", }, params: {}, body: {}, } as AuthenticatedRequest; // Mock workspace membership does not exist vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue(null); await expect( workspaceGuard.canActivate({ switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any) ).rejects.toThrow(ForbiddenException); }); it("should deny access when workspace ID is missing", async () => { const mockRequest: Partial = { user: { id: "user-123", email: "test@example.com", }, headers: {}, params: {}, body: {}, } as AuthenticatedRequest; await expect( workspaceGuard.canActivate({ switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any) ).rejects.toThrow("Workspace ID is required"); }); it("should check workspace ID from URL parameter", async () => { const mockRequest: Partial = { user: { id: "user-123", email: "test@example.com", }, headers: {}, params: { workspaceId: "workspace-789", }, } as any; vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue({ workspaceId: "workspace-789", userId: "user-123", role: "MEMBER", createdAt: new Date(), updatedAt: new Date(), } as any); const canActivate = await workspaceGuard.canActivate({ switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any); expect(canActivate).toBe(true); expect(prismaService.workspaceMember.findUnique).toHaveBeenCalledWith({ where: { workspaceId_userId: { workspaceId: "workspace-789", userId: "user-123", }, }, }); }); it("should check workspace ID from request body", async () => { const mockRequest: Partial = { user: { id: "user-123", email: "test@example.com", }, headers: {}, params: {}, body: { workspaceId: "workspace-111", }, } as any; vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue({ workspaceId: "workspace-111", userId: "user-123", role: "ADMIN", createdAt: new Date(), updatedAt: new Date(), } as any); const canActivate = await workspaceGuard.canActivate({ switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any); expect(canActivate).toBe(true); }); }); describe("Workspace isolation", () => { it("should prevent cross-workspace access", async () => { const mockRequest: Partial = { user: { id: "user-123", email: "test@example.com", }, headers: { "x-workspace-id": "workspace-attacker", }, params: {}, body: {}, } as AuthenticatedRequest; // User is NOT a member of the requested workspace vi.spyOn(prismaService.workspaceMember, "findUnique").mockResolvedValue(null); await expect( workspaceGuard.canActivate({ switchToHttp: () => ({ getRequest: () => mockRequest, }), } as any) ).rejects.toThrow(ForbiddenException); }); }); });