diff --git a/src/admin/auth/__tests__/useAuth.test.js b/src/admin/auth/__tests__/useAuth.test.js new file mode 100644 index 0000000..3343857 --- /dev/null +++ b/src/admin/auth/__tests__/useAuth.test.js @@ -0,0 +1,99 @@ +import { renderHook, act } from '@testing-library/react'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +const { + mockUnsubscribe, + mockGetSession, + mockOnAuthStateChange, + mockSignInWithPassword, + mockSignOut, +} = vi.hoisted(() => ({ + mockUnsubscribe: vi.fn(), + mockGetSession: vi.fn(), + mockOnAuthStateChange: vi.fn(() => ({ + data: { subscription: { unsubscribe: vi.fn() } }, + })), + mockSignInWithPassword: vi.fn(), + mockSignOut: vi.fn(), +})); + +vi.mock('../../../lib/supabase', () => ({ + supabase: { + auth: { + getSession: mockGetSession, + onAuthStateChange: mockOnAuthStateChange, + signInWithPassword: mockSignInWithPassword, + signOut: mockSignOut, + }, + }, +})); + +import { useAuth } from '../useAuth'; + +describe('useAuth', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockOnAuthStateChange.mockReturnValue({ + data: { subscription: { unsubscribe: mockUnsubscribe } }, + }); + }); + + it('starts with loading=true and session=null', () => { + mockGetSession.mockReturnValue(new Promise(() => {})); + const { result } = renderHook(() => useAuth()); + expect(result.current.loading).toBe(true); + expect(result.current.session).toBe(null); + }); + + it('sets loading=false and session after getSession resolves', async () => { + const fakeSession = { user: { email: 'admin@test.com' } }; + mockGetSession.mockResolvedValue({ data: { session: fakeSession } }); + const { result } = renderHook(() => useAuth()); + await act(async () => {}); + expect(result.current.loading).toBe(false); + expect(result.current.session).toEqual(fakeSession); + }); + + it('login calls signInWithPassword with email and password', async () => { + mockGetSession.mockResolvedValue({ data: { session: null } }); + mockSignInWithPassword.mockResolvedValue({ error: null }); + const { result } = renderHook(() => useAuth()); + await act(async () => { + await result.current.login('admin@test.com', 'secret'); + }); + expect(mockSignInWithPassword).toHaveBeenCalledWith({ + email: 'admin@test.com', + password: 'secret', + }); + }); + + it('login returns error when credentials are wrong', async () => { + mockGetSession.mockResolvedValue({ data: { session: null } }); + const fakeError = new Error('Invalid credentials'); + mockSignInWithPassword.mockResolvedValue({ error: fakeError }); + const { result } = renderHook(() => useAuth()); + let loginResult; + await act(async () => { + loginResult = await result.current.login('bad@test.com', 'wrong'); + }); + expect(loginResult.error).toBe(fakeError); + }); + + it('logout calls signOut', async () => { + mockGetSession.mockResolvedValue({ data: { session: null } }); + mockSignOut.mockResolvedValue({}); + const { result } = renderHook(() => useAuth()); + await act(async () => { + await result.current.logout(); + }); + expect(mockSignOut).toHaveBeenCalled(); + }); + + it('unsubscribes on unmount', async () => { + mockGetSession.mockResolvedValue({ data: { session: null } }); + const { unmount } = renderHook(() => useAuth()); + await act(async () => {}); + unmount(); + expect(mockUnsubscribe).toHaveBeenCalled(); + }); +}); diff --git a/src/admin/auth/useAuth.js b/src/admin/auth/useAuth.js new file mode 100644 index 0000000..d8487c3 --- /dev/null +++ b/src/admin/auth/useAuth.js @@ -0,0 +1,31 @@ +import { useState, useEffect } from 'react'; +import { supabase } from '../../lib/supabase'; + +export function useAuth() { + const [session, setSession] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session); + setLoading(false); + }); + + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setSession(session); + }); + + return () => subscription.unsubscribe(); + }, []); + + const login = async (email, password) => { + const { error } = await supabase.auth.signInWithPassword({ email, password }); + return { error }; + }; + + const logout = async () => { + await supabase.auth.signOut(); + }; + + return { session, loading, login, logout }; +}