Files
aplp.frontend.web/src/features/auth/store/authStore.test.ts
T

100 lines
3.3 KiB
TypeScript
Raw Normal View History

2026-08-11 21:09:32 +07:00
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { tokenStorage } from '@/api/tokenStorage';
import { authApi } from '@/features/auth/api/authApi';
import { useAuthStore } from '@/features/auth/store/authStore';
vi.mock('@/features/auth/api/authApi', () => ({
authApi: {
login: vi.fn(),
logout: vi.fn(),
refresh: vi.fn(),
getMe: vi.fn(),
listUsers: vi.fn(),
},
}));
const mockedAuthApi = vi.mocked(authApi);
describe('authStore', () => {
beforeEach(() => {
tokenStorage.clear();
useAuthStore.setState({ user: null, isInitializing: true });
vi.clearAllMocks();
mockedAuthApi.login.mockResolvedValue({
accessToken: 'access-123',
2026-08-11 23:10:00 +07:00
refreshToken: 'refresh-123',
2026-08-11 21:09:32 +07:00
user: { id: 'u1', email: 'learner@aplp.io', name: 'Learner' },
});
mockedAuthApi.refresh.mockResolvedValue({
accessToken: 'access-refreshed',
2026-08-11 23:10:00 +07:00
refreshToken: 'refresh-refreshed',
2026-08-11 21:09:32 +07:00
user: { id: 'u1', email: 'learner@aplp.io', name: 'Learner' },
});
});
it('login stores token and user', async () => {
2026-08-22 08:24:06 +07:00
await useAuthStore.getState().login('learner', 'secret');
2026-08-11 21:09:32 +07:00
expect(mockedAuthApi.login).toHaveBeenCalledWith({
2026-08-22 08:24:06 +07:00
username: 'learner',
2026-08-11 21:09:32 +07:00
password: 'secret',
});
expect(tokenStorage.get()).toBe('access-123');
expect(useAuthStore.getState().user?.email).toBe('learner@aplp.io');
expect(useAuthStore.getState().isInitializing).toBe(false);
});
it('login failure leaves user signed out', async () => {
mockedAuthApi.login.mockRejectedValue(new Error('invalid credentials'));
2026-08-22 08:24:06 +07:00
await expect(useAuthStore.getState().login('learner', 'wrong')).rejects.toThrow(
2026-08-11 21:09:32 +07:00
'invalid credentials',
);
expect(tokenStorage.get()).toBeNull();
expect(useAuthStore.getState().user).toBeNull();
});
it('logout clears token and user even when API call fails', async () => {
2026-08-22 08:24:06 +07:00
await useAuthStore.getState().login('learner', 'secret');
2026-08-11 21:09:32 +07:00
mockedAuthApi.logout.mockRejectedValue(new Error('network'));
await useAuthStore.getState().logout();
expect(tokenStorage.get()).toBeNull();
expect(useAuthStore.getState().user).toBeNull();
});
it('init restores session from refresh token', async () => {
2026-08-11 23:10:00 +07:00
tokenStorage.set('old-access', 'refresh-123');
2026-08-11 21:09:32 +07:00
await useAuthStore.getState().init();
expect(mockedAuthApi.refresh).toHaveBeenCalledOnce();
2026-08-11 23:10:00 +07:00
expect(mockedAuthApi.refresh).toHaveBeenCalledWith('refresh-123');
2026-08-11 21:09:32 +07:00
expect(tokenStorage.get()).toBe('access-refreshed');
expect(useAuthStore.getState().user?.id).toBe('u1');
expect(useAuthStore.getState().isInitializing).toBe(false);
});
2026-08-11 23:10:00 +07:00
it('init skips refresh when no refresh token is stored', async () => {
await useAuthStore.getState().init();
expect(mockedAuthApi.refresh).not.toHaveBeenCalled();
expect(tokenStorage.get()).toBeNull();
expect(useAuthStore.getState().user).toBeNull();
expect(useAuthStore.getState().isInitializing).toBe(false);
});
2026-08-11 21:09:32 +07:00
it('init handles expired refresh token gracefully', async () => {
2026-08-11 23:10:00 +07:00
tokenStorage.set('old-access', 'refresh-expired');
2026-08-11 21:09:32 +07:00
mockedAuthApi.refresh.mockRejectedValue(new Error('401'));
await useAuthStore.getState().init();
expect(tokenStorage.get()).toBeNull();
expect(useAuthStore.getState().user).toBeNull();
expect(useAuthStore.getState().isInitializing).toBe(false);
});
});