- 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>
157 lines
5.9 KiB
TypeScript
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);
|
|
});
|
|
});
|