import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createSessionPermissionChecks, withAdminFallback, canDeleteByOwner, } from '@/services/session-permissions'; vi.mock('@/services/teams', () => ({ isAdminOfUser: vi.fn(), })); const { isAdminOfUser } = await import('@/services/teams'); const mockIsAdminOfUser = vi.mocked(isAdminOfUser); // Factory for mock Prisma model delegate function makeModel(countResult: number, ownerId: string | null = 'owner-1') { return { count: vi.fn().mockResolvedValue(countResult), findUnique: vi.fn().mockResolvedValue(ownerId ? { userId: ownerId } : null), }; } describe('createSessionPermissionChecks', () => { beforeEach(() => { vi.clearAllMocks(); mockIsAdminOfUser.mockResolvedValue(false); }); describe('canAccess', () => { it('returns true when user has direct access (count > 0)', async () => { const { canAccess } = createSessionPermissionChecks(makeModel(1)); expect(await canAccess('session-1', 'user-1')).toBe(true); }); it('returns true when no direct access but user is team admin', async () => { mockIsAdminOfUser.mockResolvedValue(true); const { canAccess } = createSessionPermissionChecks(makeModel(0, 'owner-1')); expect(await canAccess('session-1', 'admin-1')).toBe(true); }); it('returns false when no direct access and not admin', async () => { const { canAccess } = createSessionPermissionChecks(makeModel(0, 'owner-1')); expect(await canAccess('session-1', 'stranger')).toBe(false); }); it('returns false when no direct access and session owner not found', async () => { const { canAccess } = createSessionPermissionChecks(makeModel(0, null)); expect(await canAccess('session-1', 'anyone')).toBe(false); }); }); describe('canEdit', () => { it('returns true when user is owner or editor (count > 0)', async () => { const { canEdit } = createSessionPermissionChecks(makeModel(1)); expect(await canEdit('session-1', 'editor-1')).toBe(true); }); it('returns false for viewer (count = 0) when not admin', async () => { const { canEdit } = createSessionPermissionChecks(makeModel(0, 'owner-1')); expect(await canEdit('session-1', 'viewer-1')).toBe(false); }); it('returns true for viewer when user is team admin', async () => { mockIsAdminOfUser.mockResolvedValue(true); const { canEdit } = createSessionPermissionChecks(makeModel(0, 'owner-1')); expect(await canEdit('session-1', 'admin-1')).toBe(true); }); }); describe('canDelete', () => { it('returns true for session owner', async () => { const { canDelete } = createSessionPermissionChecks(makeModel(1, 'owner-1')); expect(await canDelete('session-1', 'owner-1')).toBe(true); }); it('returns false for non-owner even with EDITOR role', async () => { const { canDelete } = createSessionPermissionChecks(makeModel(1, 'owner-1')); expect(await canDelete('session-1', 'editor-1')).toBe(false); }); it('returns true when user is team admin of the owner', async () => { mockIsAdminOfUser.mockResolvedValue(true); const { canDelete } = createSessionPermissionChecks(makeModel(0, 'owner-1')); expect(await canDelete('session-1', 'admin-1')).toBe(true); }); it('returns false when session not found', async () => { const { canDelete } = createSessionPermissionChecks(makeModel(0, null)); expect(await canDelete('session-1', 'anyone')).toBe(false); }); }); }); describe('withAdminFallback', () => { beforeEach(() => { vi.clearAllMocks(); mockIsAdminOfUser.mockResolvedValue(false); }); it('returns true immediately when hasDirectAccess is true (no admin check)', async () => { const getOwnerId = vi.fn(); const result = await withAdminFallback(true, getOwnerId, 'session-1', 'user-1'); expect(result).toBe(true); expect(getOwnerId).not.toHaveBeenCalled(); expect(mockIsAdminOfUser).not.toHaveBeenCalled(); }); it('falls back to admin check when no direct access', async () => { mockIsAdminOfUser.mockResolvedValue(true); const getOwnerId = vi.fn().mockResolvedValue('owner-1'); const result = await withAdminFallback(false, getOwnerId, 'session-1', 'admin-1'); expect(result).toBe(true); expect(mockIsAdminOfUser).toHaveBeenCalledWith('owner-1', 'admin-1'); }); it('returns false when no direct access and not admin', async () => { const getOwnerId = vi.fn().mockResolvedValue('owner-1'); const result = await withAdminFallback(false, getOwnerId, 'session-1', 'stranger'); expect(result).toBe(false); }); it('returns false when owner not found', async () => { const getOwnerId = vi.fn().mockResolvedValue(null); const result = await withAdminFallback(false, getOwnerId, 'session-1', 'anyone'); expect(result).toBe(false); expect(mockIsAdminOfUser).not.toHaveBeenCalled(); }); }); describe('canDeleteByOwner', () => { beforeEach(() => { vi.clearAllMocks(); mockIsAdminOfUser.mockResolvedValue(false); }); it('returns true when userId matches ownerId', async () => { const getOwnerId = vi.fn().mockResolvedValue('user-1'); expect(await canDeleteByOwner(getOwnerId, 'session-1', 'user-1')).toBe(true); }); it('returns false when userId does not match and not admin', async () => { const getOwnerId = vi.fn().mockResolvedValue('owner-1'); expect(await canDeleteByOwner(getOwnerId, 'session-1', 'other')).toBe(false); }); it('returns true when user is admin of owner', async () => { mockIsAdminOfUser.mockResolvedValue(true); const getOwnerId = vi.fn().mockResolvedValue('owner-1'); expect(await canDeleteByOwner(getOwnerId, 'session-1', 'admin-1')).toBe(true); }); it('returns false when session not found (ownerId is null)', async () => { const getOwnerId = vi.fn().mockResolvedValue(null); expect(await canDeleteByOwner(getOwnerId, 'session-1', 'anyone')).toBe(false); }); });