import { describe, it, expect, vi } from 'vitest'; import { fetchEvents, fetchEvent, fetchEventBySlug, createEvent, updateEvent, deleteEvent, fetchEventMembers, addEventMember, removeEventMember, fetchEventRoles, createEventRole, updateEventRole, deleteEventRole, fetchEventDepartments, createEventDepartment, updateEventDepartment, updateDepartmentPlannedBudget, deleteEventDepartment, assignMemberDepartment, unassignMemberDepartment, } from './events'; // ── Supabase mock builder ──────────────────────────────────────────────────── function mockChain(resolvedValue: { data: any; error: any }) { const chain: any = {}; const methods = ['from', 'select', 'insert', 'upsert', 'update', 'delete', 'eq', 'in', 'like', 'order', 'single']; for (const m of methods) { chain[m] = vi.fn(() => chain); } chain.single = vi.fn(() => Promise.resolve(resolvedValue)); chain.then = (resolve: any) => resolve(resolvedValue); return chain; } function mockSupabase(resolvedValue: { data: any; error: any }) { const chain = mockChain(resolvedValue); return { from: vi.fn(() => chain), _chain: chain } as any; } // ── fetchEvents ────────────────────────────────────────────────────────────── describe('fetchEvents', () => { it('returns events with member counts', async () => { const raw = [{ id: 'e1', name: 'Conf', event_members: [{ count: 5 }] }]; const sb = mockSupabase({ data: raw, error: null }); const result = await fetchEvents(sb, 'o1'); expect(result).toHaveLength(1); expect(result[0].member_count).toBe(5); }); it('returns empty array when null', async () => { const sb = mockSupabase({ data: null, error: null }); expect(await fetchEvents(sb, 'o1')).toEqual([]); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(fetchEvents(sb, 'o1')).rejects.toEqual({ message: 'fail' }); }); }); // ── fetchEvent ─────────────────────────────────────────────────────────────── describe('fetchEvent', () => { it('returns event by id', async () => { const event = { id: 'e1', name: 'Conf' }; const sb = mockSupabase({ data: event, error: null }); expect(await fetchEvent(sb, 'e1')).toEqual(event); }); it('returns null when not found', async () => { const sb = mockSupabase({ data: null, error: { code: 'PGRST116', message: 'not found' } }); expect(await fetchEvent(sb, 'e1')).toBeNull(); }); it('throws on other errors', async () => { const sb = mockSupabase({ data: null, error: { code: '42000', message: 'fail' } }); await expect(fetchEvent(sb, 'e1')).rejects.toEqual({ code: '42000', message: 'fail' }); }); }); // ── fetchEventBySlug ───────────────────────────────────────────────────────── describe('fetchEventBySlug', () => { it('returns event by slug', async () => { const event = { id: 'e1', slug: 'conf' }; const sb = mockSupabase({ data: event, error: null }); expect(await fetchEventBySlug(sb, 'o1', 'conf')).toEqual(event); }); it('returns null when not found', async () => { const sb = mockSupabase({ data: null, error: { code: 'PGRST116', message: 'not found' } }); expect(await fetchEventBySlug(sb, 'o1', 'nope')).toBeNull(); }); it('throws on other errors', async () => { const sb = mockSupabase({ data: null, error: { code: '42000', message: 'fail' } }); await expect(fetchEventBySlug(sb, 'o1', 'x')).rejects.toEqual({ code: '42000', message: 'fail' }); }); }); // ── createEvent ────────────────────────────────────────────────────────────── describe('createEvent', () => { it('creates event with unique slug', async () => { const event = { id: 'e1', name: 'Conf', slug: 'conf' }; // Two calls: slug check + insert const chain: any = {}; const methods = ['from', 'select', 'insert', 'eq', 'like', 'order', 'single']; for (const m of methods) { chain[m] = vi.fn(() => chain); } let callIdx = 0; chain.then = (resolve: any) => { callIdx++; if (callIdx === 1) return resolve({ data: [], error: null }); // no existing slugs return resolve({ data: event, error: null }); }; chain.single = vi.fn(() => Promise.resolve({ data: event, error: null })); const sb = { from: vi.fn(() => chain) } as any; const result = await createEvent(sb, 'o1', 'u1', { name: 'Conf' }); expect(result).toEqual(event); }); it('throws on insert error', async () => { const chain: any = {}; const methods = ['from', 'select', 'insert', 'eq', 'like', 'order', 'single']; for (const m of methods) { chain[m] = vi.fn(() => chain); } let callIdx = 0; chain.then = (resolve: any) => { callIdx++; if (callIdx === 1) return resolve({ data: [], error: null }); return resolve({ data: null, error: { message: 'fail' } }); }; chain.single = vi.fn(() => Promise.resolve({ data: null, error: { message: 'fail' } })); const sb = { from: vi.fn(() => chain) } as any; await expect(createEvent(sb, 'o1', 'u1', { name: 'X' })).rejects.toEqual({ message: 'fail' }); }); }); // ── updateEvent ────────────────────────────────────────────────────────────── describe('updateEvent', () => { it('updates and returns event', async () => { const event = { id: 'e1', name: 'Updated' }; const sb = mockSupabase({ data: event, error: null }); expect(await updateEvent(sb, 'e1', { name: 'Updated' })).toEqual(event); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(updateEvent(sb, 'e1', { name: 'X' })).rejects.toEqual({ message: 'fail' }); }); }); // ── deleteEvent ────────────────────────────────────────────────────────────── describe('deleteEvent', () => { it('deletes without error', async () => { const sb = mockSupabase({ data: null, error: null }); await expect(deleteEvent(sb, 'e1')).resolves.toBeUndefined(); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(deleteEvent(sb, 'e1')).rejects.toEqual({ message: 'fail' }); }); }); // ── fetchEventMembers ──────────────────────────────────────────────────────── describe('fetchEventMembers', () => { it('returns empty array when no members', async () => { const sb = mockSupabase({ data: [], error: null }); expect(await fetchEventMembers(sb, 'e1')).toEqual([]); }); it('returns members with profiles, roles, and departments', async () => { const members = [{ id: 'm1', event_id: 'e1', user_id: 'u1', role_id: 'r1' }]; const profiles = [{ id: 'u1', email: 'a@b.com', full_name: 'Alice' }]; const roles = [{ id: 'r1', event_id: 'e1', name: 'Lead' }]; const memberDepts = [{ event_member_id: 'm1', department_id: 'd1' }]; const departments = [{ id: 'd1', event_id: 'e1', name: 'Logistics' }]; const chain: any = {}; const methods = ['from', 'select', 'eq', 'in', 'order']; for (const m of methods) { chain[m] = vi.fn(() => chain); } let callIdx = 0; chain.then = (resolve: any) => { callIdx++; switch (callIdx) { case 1: return resolve({ data: members, error: null }); // members case 2: return resolve({ data: profiles, error: null }); // profiles case 3: return resolve({ data: roles, error: null }); // roles case 4: return resolve({ data: memberDepts, error: null }); // member-depts case 5: return resolve({ data: departments, error: null }); // departments default: return resolve({ data: [], error: null }); } }; const sb = { from: vi.fn(() => chain) } as any; const result = await fetchEventMembers(sb, 'e1'); expect(result).toHaveLength(1); expect(result[0].profile?.full_name).toBe('Alice'); expect(result[0].event_role?.name).toBe('Lead'); expect(result[0].departments).toHaveLength(1); expect(result[0].departments[0].name).toBe('Logistics'); }); it('throws on error', async () => { const chain: any = {}; const methods = ['from', 'select', 'eq', 'in', 'order']; for (const m of methods) { chain[m] = vi.fn(() => chain); } chain.then = (resolve: any) => resolve({ data: null, error: { message: 'fail' } }); const sb = { from: vi.fn(() => chain) } as any; await expect(fetchEventMembers(sb, 'e1')).rejects.toEqual({ message: 'fail' }); }); }); // ── addEventMember ─────────────────────────────────────────────────────────── describe('addEventMember', () => { it('adds member', async () => { const member = { id: 'm1', event_id: 'e1', user_id: 'u1', role: 'member' }; const sb = mockSupabase({ data: member, error: null }); expect(await addEventMember(sb, 'e1', 'u1')).toEqual(member); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(addEventMember(sb, 'e1', 'u1')).rejects.toEqual({ message: 'fail' }); }); }); // ── removeEventMember ──────────────────────────────────────────────────────── describe('removeEventMember', () => { it('removes without error', async () => { const sb = mockSupabase({ data: null, error: null }); await expect(removeEventMember(sb, 'e1', 'u1')).resolves.toBeUndefined(); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(removeEventMember(sb, 'e1', 'u1')).rejects.toEqual({ message: 'fail' }); }); }); // ── Event Roles ────────────────────────────────────────────────────────────── describe('fetchEventRoles', () => { it('returns roles', async () => { const roles = [{ id: 'r1', name: 'Lead' }]; const sb = mockSupabase({ data: roles, error: null }); expect(await fetchEventRoles(sb, 'e1')).toEqual(roles); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(fetchEventRoles(sb, 'e1')).rejects.toEqual({ message: 'fail' }); }); }); describe('createEventRole', () => { it('creates role', async () => { const role = { id: 'r1', name: 'Lead', color: '#6366f1' }; const sb = mockSupabase({ data: role, error: null }); expect(await createEventRole(sb, 'e1', { name: 'Lead' })).toEqual(role); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(createEventRole(sb, 'e1', { name: 'X' })).rejects.toEqual({ message: 'fail' }); }); }); describe('updateEventRole', () => { it('updates role', async () => { const role = { id: 'r1', name: 'Updated' }; const sb = mockSupabase({ data: role, error: null }); expect(await updateEventRole(sb, 'r1', { name: 'Updated' })).toEqual(role); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(updateEventRole(sb, 'r1', { name: 'X' })).rejects.toEqual({ message: 'fail' }); }); }); describe('deleteEventRole', () => { it('deletes without error', async () => { const sb = mockSupabase({ data: null, error: null }); await expect(deleteEventRole(sb, 'r1')).resolves.toBeUndefined(); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(deleteEventRole(sb, 'r1')).rejects.toEqual({ message: 'fail' }); }); }); // ── Event Departments ──────────────────────────────────────────────────────── describe('fetchEventDepartments', () => { it('returns departments', async () => { const depts = [{ id: 'd1', name: 'Logistics' }]; const sb = mockSupabase({ data: depts, error: null }); expect(await fetchEventDepartments(sb, 'e1')).toEqual(depts); }); it('returns empty array when null', async () => { const sb = mockSupabase({ data: null, error: null }); expect(await fetchEventDepartments(sb, 'e1')).toEqual([]); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(fetchEventDepartments(sb, 'e1')).rejects.toEqual({ message: 'fail' }); }); }); describe('createEventDepartment', () => { it('creates department', async () => { const dept = { id: 'd1', name: 'Marketing', color: '#00A3E0' }; const sb = mockSupabase({ data: dept, error: null }); expect(await createEventDepartment(sb, 'e1', { name: 'Marketing' })).toEqual(dept); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(createEventDepartment(sb, 'e1', { name: 'X' })).rejects.toEqual({ message: 'fail' }); }); }); describe('updateEventDepartment', () => { it('updates department', async () => { const dept = { id: 'd1', name: 'Updated' }; const sb = mockSupabase({ data: dept, error: null }); expect(await updateEventDepartment(sb, 'd1', { name: 'Updated' })).toEqual(dept); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(updateEventDepartment(sb, 'd1', { name: 'X' })).rejects.toEqual({ message: 'fail' }); }); }); describe('updateDepartmentPlannedBudget', () => { it('updates planned budget', async () => { const dept = { id: 'd1', planned_budget: 5000 }; const sb = mockSupabase({ data: dept, error: null }); expect(await updateDepartmentPlannedBudget(sb, 'd1', 5000)).toEqual(dept); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(updateDepartmentPlannedBudget(sb, 'd1', 0)).rejects.toEqual({ message: 'fail' }); }); }); describe('deleteEventDepartment', () => { it('deletes without error', async () => { const sb = mockSupabase({ data: null, error: null }); await expect(deleteEventDepartment(sb, 'd1')).resolves.toBeUndefined(); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(deleteEventDepartment(sb, 'd1')).rejects.toEqual({ message: 'fail' }); }); }); // ── Member-Department Assignments ──────────────────────────────────────────── describe('assignMemberDepartment', () => { it('assigns member to department', async () => { const md = { id: 'md1', event_member_id: 'm1', department_id: 'd1' }; const sb = mockSupabase({ data: md, error: null }); expect(await assignMemberDepartment(sb, 'm1', 'd1')).toEqual(md); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(assignMemberDepartment(sb, 'm1', 'd1')).rejects.toEqual({ message: 'fail' }); }); }); describe('unassignMemberDepartment', () => { it('unassigns without error', async () => { const sb = mockSupabase({ data: null, error: null }); await expect(unassignMemberDepartment(sb, 'm1', 'd1')).resolves.toBeUndefined(); }); it('throws on error', async () => { const sb = mockSupabase({ data: null, error: { message: 'fail' } }); await expect(unassignMemberDepartment(sb, 'm1', 'd1')).rejects.toEqual({ message: 'fail' }); }); });