import { describe, it, expect, vi, beforeEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useAuthStore } from '../../stores/auth.store' // $fetch and navigateTo are shimmed in tests/setup.ts as globals. // We re-cast them here so vi.mocked() provides typed mock utilities. const mockFetch = vi.mocked($fetch as ReturnType) const mockNavigateTo = vi.mocked(navigateTo as ReturnType) beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() }) // --------------------------------------------------------------------------- // login() // --------------------------------------------------------------------------- describe('login()', () => { it('stores the user and returns the response on success', async () => { const user = { id: 1, email: 'alice@example.com', displayName: 'Alice', role: 'user' } mockFetch.mockResolvedValueOnce({ user }) const auth = useAuthStore() const result = await auth.login('alice@example.com', 'secret') expect(mockFetch).toHaveBeenCalledWith('/api/auth/login', { method: 'POST', body: { email: 'alice@example.com', password: 'secret' }, }) expect(auth.user).toEqual(user) expect(result).toEqual({ user }) }) it('includes totpCode in body when provided', async () => { const user = { id: 1, email: 'alice@example.com', displayName: 'Alice', role: 'user' } mockFetch.mockResolvedValueOnce({ user }) const auth = useAuthStore() await auth.login('alice@example.com', 'secret', '123456') expect(mockFetch).toHaveBeenCalledWith('/api/auth/login', { method: 'POST', body: { email: 'alice@example.com', password: 'secret', totpCode: '123456' }, }) }) it('returns requires_totp and does NOT set user when TOTP is required', async () => { mockFetch.mockResolvedValueOnce({ requires_totp: true }) const auth = useAuthStore() const result = await auth.login('alice@example.com', 'secret') expect(result).toEqual({ requires_totp: true }) expect(auth.user).toBeNull() }) it('does NOT include an Authorization header (cookie-only auth)', async () => { mockFetch.mockResolvedValueOnce({ user: { id: 1, email: 'a@b.com', displayName: null, role: 'user' } }) const auth = useAuthStore() await auth.login('a@b.com', 'pass') const callArgs = mockFetch.mock.calls[0] const options = callArgs[1] as Record expect(options).not.toHaveProperty('headers') }) }) // --------------------------------------------------------------------------- // logout() // --------------------------------------------------------------------------- describe('logout()', () => { it('clears user state and calls navigateTo("/login")', async () => { mockFetch.mockResolvedValueOnce({}) // logout POST succeeds const auth = useAuthStore() auth.user = { id: 1, email: 'alice@example.com', displayName: 'Alice', role: 'user' } await auth.logout() expect(auth.user).toBeNull() expect(mockNavigateTo).toHaveBeenCalledWith('/login') }) it('clears user state even when the logout request fails', async () => { mockFetch.mockRejectedValueOnce(new Error('network error')) const auth = useAuthStore() auth.user = { id: 1, email: 'alice@example.com', displayName: 'Alice', role: 'user' } await auth.logout() expect(auth.user).toBeNull() expect(mockNavigateTo).toHaveBeenCalledWith('/login') }) }) // --------------------------------------------------------------------------- // fetchProfile() // --------------------------------------------------------------------------- describe('fetchProfile()', () => { it('sets user on success', async () => { const user = { id: 2, email: 'bob@example.com', displayName: 'Bob', role: 'admin' } mockFetch.mockResolvedValueOnce(user) const auth = useAuthStore() await auth.fetchProfile() expect(auth.user).toEqual(user) expect(mockFetch).toHaveBeenCalledWith('/api/auth/profile') }) it('sets user to null on failure', async () => { mockFetch.mockRejectedValueOnce(new Error('401')) const auth = useAuthStore() auth.user = { id: 1, email: 'a@b.com', displayName: null, role: 'user' } await auth.fetchProfile() expect(auth.user).toBeNull() }) }) // --------------------------------------------------------------------------- // getWsTicket() // --------------------------------------------------------------------------- describe('getWsTicket()', () => { it('returns the ticket string from the API', async () => { mockFetch.mockResolvedValueOnce({ ticket: 'abc-xyz-ticket' }) const auth = useAuthStore() const ticket = await auth.getWsTicket() expect(ticket).toBe('abc-xyz-ticket') expect(mockFetch).toHaveBeenCalledWith('/api/auth/ws-ticket', { method: 'POST' }) }) }) // --------------------------------------------------------------------------- // getters // --------------------------------------------------------------------------- describe('isAuthenticated getter', () => { it('returns false when user is null', () => { const auth = useAuthStore() expect(auth.isAuthenticated).toBe(false) }) it('returns true when user is set', () => { const auth = useAuthStore() auth.user = { id: 1, email: 'a@b.com', displayName: null, role: 'user' } expect(auth.isAuthenticated).toBe(true) }) }) describe('isAdmin getter', () => { it('returns false for non-admin role', () => { const auth = useAuthStore() auth.user = { id: 1, email: 'a@b.com', displayName: null, role: 'user' } expect(auth.isAdmin).toBe(false) }) it('returns true for admin role', () => { const auth = useAuthStore() auth.user = { id: 1, email: 'a@b.com', displayName: null, role: 'admin' } expect(auth.isAdmin).toBe(true) }) })