import { describe, it, expect, beforeEach, vi } from 'vitest'; import { setActivePinia, createPinia } from 'pinia'; // Mock apiError to prevent side effects (imports router, pinia, toast) vi.mock('@/module/apiError.js', () => ({ default: vi.fn(), })); import axios from 'axios'; import useLoginStore from '@/stores/login.ts'; // Mock axios methods vi.spyOn(axios, 'post').mockImplementation(vi.fn()); vi.spyOn(axios, 'get').mockImplementation(vi.fn()); describe('loginStore', () => { let store; beforeEach(() => { setActivePinia(createPinia()); store = useLoginStore(); store.$router = { push: vi.fn() }; vi.clearAllMocks(); // Clear cookies document.cookie.split(';').forEach((c) => { const name = c.split('=')[0].trim(); if (name) { document.cookie = name + '=; Max-Age=-99999999; path=/'; } }); }); it('has correct default state', () => { expect(store.auth.grant_type).toBe('password'); expect(store.auth.username).toBe(''); expect(store.isLoggedIn).toBe(false); expect(store.isInvalid).toBe(false); }); describe('signIn', () => { it('stores token and navigates on success', async () => { axios.post.mockResolvedValue({ data: { access_token: 'test-access-token', refresh_token: 'test-refresh-token', }, }); await store.signIn(); expect(axios.post).toHaveBeenCalledWith( '/api/oauth/token', store.auth, expect.objectContaining({ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, }), ); expect(store.isLoggedIn).toBe(true); expect(document.cookie).toContain('luciaToken=test-access-token'); expect(store.$router.push).toHaveBeenCalledWith('/files'); }); it('redirects to remembered URL when set', async () => { axios.post.mockResolvedValue({ data: { access_token: 'token', refresh_token: 'refresh', }, }); // btoa('/dashboard') = 'L2Rhc2hib2FyZA==' store.rememberedReturnToUrl = btoa('/dashboard'); // Mock window.location.href setter const originalLocation = window.location; delete window.location; window.location = { href: '' }; await store.signIn(); expect(window.location.href).toBe('/dashboard'); window.location = originalLocation; }); it('sets isInvalid on error', async () => { axios.post.mockRejectedValue(new Error('401')); await store.signIn(); expect(store.isInvalid).toBe(true); }); }); describe('logOut', () => { it('clears auth state and navigates to login', () => { store.isLoggedIn = true; store.logOut(); expect(store.isLoggedIn).toBe(false); expect(store.$router.push).toHaveBeenCalledWith('/login'); }); }); describe('getUserData', () => { it('stores user data on success', async () => { axios.get.mockResolvedValue({ data: { username: 'testuser', name: 'Test User' }, }); await store.getUserData(); expect(axios.get).toHaveBeenCalledWith('/api/my-account'); expect(store.userData).toEqual({ username: 'testuser', name: 'Test User', }); }); }); describe('checkLogin', () => { it('does not redirect on success', async () => { axios.get.mockResolvedValue({ data: {} }); await store.checkLogin(); expect(store.$router.push).not.toHaveBeenCalled(); }); it('redirects to login on error', async () => { axios.get.mockRejectedValue(new Error('401')); await store.checkLogin(); expect(store.$router.push).toHaveBeenCalledWith('/login'); }); }); it('setRememberedReturnToUrl stores URL', () => { store.setRememberedReturnToUrl('abc'); expect(store.rememberedReturnToUrl).toBe('abc'); }); it('setIsLoggedIn sets boolean', () => { store.setIsLoggedIn(true); expect(store.isLoggedIn).toBe(true); }); describe('expired', () => { it('is approximately 6 months in the future', () => { const now = new Date(); const sixMonthsLater = new Date(now); sixMonthsLater.setMonth(sixMonthsLater.getMonth() + 6); const expiredDate = new Date(store.expired); // Allow 1 day tolerance const diffMs = Math.abs(expiredDate.getTime() - sixMonthsLater.getTime()); expect(diffMs).toBeLessThan(24 * 60 * 60 * 1000); }); }); });