Files
lucia-frontend/tests/stores/login.test.js

160 lines
4.4 KiB
JavaScript

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);
});
});
});