/** * Unit Tests: User Management * * Tests for user name editing and profile management */ import { describe, test, expect, mock, beforeEach,} from 'bun:test'; import { hasRole } from '@/lib/permissions'; import { getSession } from '@/lib/auth-simple'; import { prisma } from '@/lib/prisma'; import type { User, Player } from '@prisma/client'; // Create mock functions at module level const getSessionMock = mock(async (): Promise => null); const userFindUniqueMock = mock(async (): Promise => null); const userUpdateMock = mock(async (): Promise => ({})); const playerFindUniqueMock = mock(async (): Promise => null); // Mock the getSession and prisma functions mock.module('@/lib/auth-simple', () => ({ getSession: getSessionMock, })); mock.module('@/lib/prisma', () => ({ prisma: { user: { findUnique: userFindUniqueMock, update: userUpdateMock, }, player: { findUnique: playerFindUniqueMock, }, }, })); // Helper to create mock user const createMockUser = (id: string, email: string, role: string, name?: string): User => ({ id, email, role, emailVerified: true, name: name || email.split('@')[0], image: null, playerId: null, createdAt: new Date(), updatedAt: new Date(), }); // Helper to create mock player const createMockPlayer = (id: number, name: string): Player => ({ id, name, normalizedName: name.toLowerCase(), rating: 0, currentElo: 1000, gamesPlayed: 0, wins: 0, losses: 0, createdAt: new Date(), updatedAt: new Date(), }); describe('User Management', () => { beforeEach(() => { // Reset mock implementations to default (no-op) before each test getSessionMock.mockImplementation(async () => undefined); userFindUniqueMock.mockImplementation(async () => undefined); userUpdateMock.mockImplementation(async () => undefined); playerFindUniqueMock.mockImplementation(async () => undefined); }); describe('User Name Editing', () => { test('club_admin should be able to edit any user name', async () => { getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); userFindUniqueMock.mockImplementation(async () => createMockUser('admin-1', 'admin@example.com', 'club_admin') ); const result = await hasRole('club_admin'); expect(result.allowed).toBe(true); }); test('tournament_admin should NOT be able to edit user names', async () => { getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); userFindUniqueMock.mockImplementation(async () => createMockUser('tour-admin-1', 'tour@example.com', 'tournament_admin') ); const result = await hasRole('club_admin'); expect(result.allowed).toBe(false); }); test('player should NOT be able to edit user names', async () => { getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); userFindUniqueMock.mockImplementation(async () => createMockUser('player-1', 'player@example.com', 'player') ); const result = await hasRole('club_admin'); expect(result.allowed).toBe(false); }); test('unauthenticated user should NOT be able to edit user names', async () => { getSessionMock.mockImplementation(async () => null); const result = await hasRole('club_admin'); expect(result.allowed).toBe(false); }); }); describe('User Profile Access', () => { test('user should be able to view their own profile', async () => { const mockUser = createMockUser('user-1', 'user@example.com', 'player'); getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); userFindUniqueMock.mockImplementation(async () => mockUser); // In the actual implementation, this would check if session.user.id === params.id const canViewOwnProfile = true; // This logic is in the API route expect(canViewOwnProfile).toBe(true); }); test('club_admin should be able to view any user profile', async () => { const mockAdmin = createMockUser('admin-1', 'admin@example.com', 'club_admin'); const mockUser = createMockUser('user-1', 'user@example.com', 'player'); getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); (userFindUniqueMock) .mockResolvedValueOnce(mockAdmin) // For the requesting user .mockResolvedValueOnce(mockUser); // For the target user // In the actual implementation, this would check if requestingUser.role === 'club_admin' const canViewOtherProfile = true; // This logic is in the API route expect(canViewOtherProfile).toBe(true); }); test('non-admin should NOT be able to view other user profiles', async () => { const mockUser = createMockUser('user-1', 'user@example.com', 'player'); getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); userFindUniqueMock.mockImplementation(async () => mockUser); // In the actual implementation, this would check if session.user.id === params.id const canViewOtherProfile = false; // This logic is in the API route expect(canViewOtherProfile).toBe(false); }); }); describe('Player Name Updates', () => { test('player name should be updated when user name is updated', async () => { const mockUser = createMockUser('user-1', 'user@example.com', 'club_admin'); const mockPlayer = createMockPlayer(1, 'Old Name'); getSessionMock.mockImplementation(async () => ({ user: { id: 'admin-1', email: 'admin@example.com' }, session: { token: 'test', expiresAt: new Date() } })); userFindUniqueMock.mockImplementation(async () => mockUser); const updatedUser = { ...mockUser, name: 'New Name', player: { ...mockPlayer, name: 'New Name', normalizedName: 'new name' } }; userUpdateMock.mockImplementation(async () => updatedUser); // The API route should update both user.name and player.name expect(updatedUser.name).toBe('New Name'); expect(updatedUser.player?.name).toBe('New Name'); }); }); });