Files
workshop-manager/src/services/__tests__/session-permissions.test.ts
Froidefond Julien f9ed732f1c test: add unit test coverage for services and lib
- 255 tests across 14 files (was 70 tests in 4 files)
- src/services/__tests__: auth (registerUser, updateUserPassword, updateUserProfile), okrs (calculateOKRProgress, createOKR, updateKeyResult, updateOKR), teams (createTeam, addTeamMember, isAdminOfUser, getTeamMemberIdsForAdminTeams, getUserTeams), weather (getPreviousWeatherEntriesForUsers, shareWeatherSessionToTeam, getWeatherSessionsHistory), workshops (createSwotItem, duplicateSwotItem, updateAction, createMotivatorSession, updateCardInfluence, addGifMoodItem, shareGifMoodSessionToTeam, getLatestEventTimestamp, cleanupOldEvents)
- src/lib/__tests__: date-utils, weather-utils, okr-utils, gravatar, workshops, share-utils
- Update vitest coverage to include src/lib/**

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 08:37:32 +01:00

157 lines
5.9 KiB
TypeScript

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);
});
});