import { describe, it, expect, vi, beforeEach } from 'vitest'; import { TokenStorage } from './tokenStorage'; // Mock dependencies vi.mock('./tokenStorage'); const mockTokenStorage = vi.mocked(TokenStorage); // Mock axios const mockPost = vi.fn(); const mockAxiosInstance = { post: mockPost, get: vi.fn(), put: vi.fn(), delete: vi.fn(), patch: vi.fn(), interceptors: { request: { use: vi.fn(), eject: vi.fn() }, response: { use: vi.fn(), eject: vi.fn() }, }, }; vi.mock('axios', () => ({ default: { create: vi.fn(() => mockAxiosInstance), }, })); // Import after mocks are set up import { refreshToken } from './tokenRefresh'; describe('tokenRefresh', () => { beforeEach(() => { vi.clearAllMocks(); mockPost.mockReset(); }); describe('refreshToken', () => { it('should refresh token successfully with direct format response', async () => { // SECURITY: Action 5.1.1.2 - Refresh token is in httpOnly cookie, // sent automatically via withCredentials. Body is empty {}. mockPost.mockResolvedValue({ data: { access_token: 'new-access-token', refresh_token: 'new-refresh-token', expires_in: 900, }, }); await refreshToken(); // Verify API call was made with empty body (cookies handle auth) expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {}); // SECURITY: setTokens is now a no-op (tokens in httpOnly cookies) expect(mockTokenStorage.setTokens).toHaveBeenCalled(); // Verify clearTokens was not called on success expect(mockTokenStorage.clearTokens).not.toHaveBeenCalled(); }); it('should refresh token successfully with wrapped format response', async () => { mockPost.mockResolvedValue({ data: { success: true, data: { access_token: 'new-access-token', refresh_token: 'new-refresh-token', expires_in: 900, }, }, }); await refreshToken(); expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {}); expect(mockTokenStorage.setTokens).toHaveBeenCalled(); expect(mockTokenStorage.clearTokens).not.toHaveBeenCalled(); }); it('should clear tokens and throw error on API failure', async () => { const apiError = new Error('API Error'); mockPost.mockRejectedValue(apiError); await expect(refreshToken()).rejects.toThrow('API Error'); // Verify API call was made expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {}); // Verify tokens were cleared on error expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1); // Verify tokens were not updated expect(mockTokenStorage.setTokens).not.toHaveBeenCalled(); }); it('should clear tokens on 401 Unauthorized error', async () => { const axiosError = { response: { status: 401, data: { error: 'Invalid refresh token' }, }, isAxiosError: true, }; mockPost.mockRejectedValue(axiosError); await expect(refreshToken()).rejects.toEqual(axiosError); // Verify tokens were cleared on 401 error expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1); }); it('should clear tokens on network error', async () => { const networkError = new Error('Network Error'); mockPost.mockRejectedValue(networkError); await expect(refreshToken()).rejects.toThrow('Network Error'); // Verify tokens were cleared on error expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1); }); it('should throw on invalid response format', async () => { mockPost.mockResolvedValue({ data: { unexpected: 'format', }, }); await expect(refreshToken()).rejects.toThrow( 'Invalid refresh response format', ); // Verify tokens were cleared on error expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1); }); }); });