import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { WorkspacesService } from "./workspaces.service"; import { PrismaService } from "../prisma/prisma.service"; import { WorkspaceMemberRole } from "@prisma/client"; describe("WorkspacesService", () => { let service: WorkspacesService; const mockUserId = "550e8400-e29b-41d4-a716-446655440001"; const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440002"; const mockWorkspace = { id: mockWorkspaceId, name: "Test Workspace", ownerId: mockUserId, settings: {}, matrixRoomId: null, createdAt: new Date("2026-01-01"), updatedAt: new Date("2026-01-01"), }; const mockMembership = { workspaceId: mockWorkspaceId, userId: mockUserId, role: WorkspaceMemberRole.OWNER, joinedAt: new Date("2026-01-01"), workspace: { id: mockWorkspaceId, name: "Test Workspace", ownerId: mockUserId, createdAt: new Date("2026-01-01"), }, }; const mockPrismaService = { workspaceMember: { findMany: vi.fn(), create: vi.fn(), }, workspace: { create: vi.fn(), }, $transaction: vi.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ WorkspacesService, { provide: PrismaService, useValue: mockPrismaService, }, ], }).compile(); service = module.get(WorkspacesService); vi.clearAllMocks(); }); describe("getUserWorkspaces", () => { it("should return all workspaces user is a member of", async () => { mockPrismaService.workspaceMember.findMany.mockResolvedValueOnce([mockMembership]); const result = await service.getUserWorkspaces(mockUserId); expect(result).toEqual([ { id: mockWorkspaceId, name: "Test Workspace", ownerId: mockUserId, role: WorkspaceMemberRole.OWNER, createdAt: mockMembership.workspace.createdAt, }, ]); expect(mockPrismaService.workspaceMember.findMany).toHaveBeenCalledWith({ where: { userId: mockUserId }, include: { workspace: { select: { id: true, name: true, ownerId: true, createdAt: true }, }, }, orderBy: { joinedAt: "asc" }, }); }); it("should return multiple workspaces ordered by joinedAt", async () => { const secondWorkspace = { ...mockMembership, workspaceId: "ws-2", role: WorkspaceMemberRole.MEMBER, joinedAt: new Date("2026-02-01"), workspace: { id: "ws-2", name: "Second Workspace", ownerId: "other-user", createdAt: new Date("2026-02-01"), }, }; mockPrismaService.workspaceMember.findMany.mockResolvedValueOnce([ mockMembership, secondWorkspace, ]); const result = await service.getUserWorkspaces(mockUserId); expect(result).toHaveLength(2); expect(result[0].id).toBe(mockWorkspaceId); expect(result[1].id).toBe("ws-2"); expect(result[1].role).toBe(WorkspaceMemberRole.MEMBER); }); it("should auto-provision a default workspace when user has no memberships", async () => { mockPrismaService.workspaceMember.findMany.mockResolvedValueOnce([]); mockPrismaService.$transaction.mockImplementationOnce( async (fn: (tx: typeof mockPrismaService) => Promise) => { const txMock = { workspaceMember: { findFirst: vi.fn().mockResolvedValueOnce(null), create: vi.fn().mockResolvedValueOnce({}), }, workspace: { create: vi.fn().mockResolvedValueOnce(mockWorkspace), }, }; return fn(txMock as unknown as typeof mockPrismaService); } ); const result = await service.getUserWorkspaces(mockUserId); expect(result).toHaveLength(1); expect(result[0].name).toBe("Test Workspace"); expect(result[0].role).toBe(WorkspaceMemberRole.OWNER); expect(mockPrismaService.$transaction).toHaveBeenCalledTimes(1); }); it("should return existing workspace if one was created between initial check and transaction", async () => { // Simulates a race condition: initial findMany returns [], but inside the // transaction another request already created a workspace. mockPrismaService.workspaceMember.findMany.mockResolvedValueOnce([]); mockPrismaService.$transaction.mockImplementationOnce( async (fn: (tx: typeof mockPrismaService) => Promise) => { const txMock = { workspaceMember: { findFirst: vi.fn().mockResolvedValueOnce(mockMembership), }, workspace: { create: vi.fn(), }, }; return fn(txMock as unknown as typeof mockPrismaService); } ); const result = await service.getUserWorkspaces(mockUserId); expect(result).toHaveLength(1); expect(result[0].id).toBe(mockWorkspaceId); expect(result[0].name).toBe("Test Workspace"); }); it("should create workspace with correct data during auto-provisioning", async () => { mockPrismaService.workspaceMember.findMany.mockResolvedValueOnce([]); let capturedWorkspaceData: unknown; let capturedMemberData: unknown; mockPrismaService.$transaction.mockImplementationOnce( async (fn: (tx: typeof mockPrismaService) => Promise) => { const txMock = { workspaceMember: { findFirst: vi.fn().mockResolvedValueOnce(null), create: vi.fn().mockImplementation((args: unknown) => { capturedMemberData = args; return {}; }), }, workspace: { create: vi.fn().mockImplementation((args: unknown) => { capturedWorkspaceData = args; return mockWorkspace; }), }, }; return fn(txMock as unknown as typeof mockPrismaService); } ); await service.getUserWorkspaces(mockUserId); expect(capturedWorkspaceData).toEqual({ data: { name: "My Workspace", ownerId: mockUserId, settings: {}, }, }); expect(capturedMemberData).toEqual({ data: { workspaceId: mockWorkspaceId, userId: mockUserId, role: WorkspaceMemberRole.OWNER, }, }); }); it("should not auto-provision when user already has workspaces", async () => { mockPrismaService.workspaceMember.findMany.mockResolvedValueOnce([mockMembership]); await service.getUserWorkspaces(mockUserId); expect(mockPrismaService.$transaction).not.toHaveBeenCalled(); }); it("should propagate database errors", async () => { mockPrismaService.workspaceMember.findMany.mockRejectedValueOnce( new Error("Database connection failed") ); await expect(service.getUserWorkspaces(mockUserId)).rejects.toThrow( "Database connection failed" ); }); }); });