28 tests across 4 spec files. Vitest + happy-dom configured with Nuxt auto-import shims ($$fetch, navigateTo, defineNuxtRouteMiddleware) so stores and composables resolve cleanly outside the Nuxt runtime. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
166 lines
5.7 KiB
TypeScript
166 lines
5.7 KiB
TypeScript
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<typeof vi.fn>)
|
|
const mockNavigateTo = vi.mocked(navigateTo as ReturnType<typeof vi.fn>)
|
|
|
|
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<string, unknown>
|
|
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)
|
|
})
|
|
})
|