2025-12-03 21:56:50 +00:00
|
|
|
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', async () => {
|
|
|
|
|
const mockRefreshToken = 'test-refresh-token';
|
|
|
|
|
const mockAccessToken = 'new-access-token';
|
|
|
|
|
const mockNewRefreshToken = 'new-refresh-token';
|
|
|
|
|
|
|
|
|
|
// Mock TokenStorage.getRefreshToken
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);
|
|
|
|
|
|
|
|
|
|
// Mock axios post response
|
|
|
|
|
mockPost.mockResolvedValue({
|
|
|
|
|
data: {
|
|
|
|
|
access_token: mockAccessToken,
|
|
|
|
|
refresh_token: mockNewRefreshToken,
|
|
|
|
|
expires_in: 900,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await refreshToken();
|
|
|
|
|
|
|
|
|
|
// Verify refresh token was retrieved
|
|
|
|
|
expect(mockTokenStorage.getRefreshToken).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
|
|
// Verify API call was made with correct parameters
|
|
|
|
|
expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {
|
|
|
|
|
refresh_token: mockRefreshToken,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Verify tokens were updated
|
|
|
|
|
expect(mockTokenStorage.setTokens).toHaveBeenCalledWith(
|
|
|
|
|
mockAccessToken,
|
2025-12-13 02:34:34 +00:00
|
|
|
mockNewRefreshToken,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Verify clearTokens was not called on success
|
|
|
|
|
expect(mockTokenStorage.clearTokens).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw error if no refresh token available', async () => {
|
|
|
|
|
// Mock TokenStorage.getRefreshToken to return null
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(null);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
await expect(refreshToken()).rejects.toThrow(
|
|
|
|
|
'No refresh token available',
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
// Verify API was not called
|
|
|
|
|
expect(mockPost).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
|
|
// Verify tokens were not updated
|
|
|
|
|
expect(mockTokenStorage.setTokens).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should clear tokens and throw error on API failure', async () => {
|
|
|
|
|
const mockRefreshToken = 'test-refresh-token';
|
|
|
|
|
const apiError = new Error('API Error');
|
|
|
|
|
|
|
|
|
|
// Mock TokenStorage.getRefreshToken
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);
|
|
|
|
|
|
|
|
|
|
mockPost.mockRejectedValue(apiError);
|
|
|
|
|
|
|
|
|
|
await expect(refreshToken()).rejects.toThrow('API Error');
|
|
|
|
|
|
|
|
|
|
// Verify API call was made
|
|
|
|
|
expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {
|
|
|
|
|
refresh_token: mockRefreshToken,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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 mockRefreshToken = 'test-refresh-token';
|
|
|
|
|
const axiosError = {
|
|
|
|
|
response: {
|
|
|
|
|
status: 401,
|
|
|
|
|
data: { error: 'Invalid refresh token' },
|
|
|
|
|
},
|
|
|
|
|
isAxiosError: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Mock TokenStorage.getRefreshToken
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);
|
|
|
|
|
|
|
|
|
|
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 400 Bad Request error', async () => {
|
|
|
|
|
const mockRefreshToken = 'test-refresh-token';
|
|
|
|
|
const axiosError = {
|
|
|
|
|
response: {
|
|
|
|
|
status: 400,
|
|
|
|
|
data: { error: 'Invalid request' },
|
|
|
|
|
},
|
|
|
|
|
isAxiosError: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Mock TokenStorage.getRefreshToken
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);
|
|
|
|
|
|
|
|
|
|
mockPost.mockRejectedValue(axiosError);
|
|
|
|
|
|
|
|
|
|
await expect(refreshToken()).rejects.toEqual(axiosError);
|
|
|
|
|
|
|
|
|
|
// Verify tokens were cleared on error
|
|
|
|
|
expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should clear tokens on network error', async () => {
|
|
|
|
|
const mockRefreshToken = 'test-refresh-token';
|
|
|
|
|
const networkError = new Error('Network Error');
|
|
|
|
|
|
|
|
|
|
// Mock TokenStorage.getRefreshToken
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);
|
|
|
|
|
|
|
|
|
|
mockPost.mockRejectedValue(networkError);
|
|
|
|
|
|
|
|
|
|
await expect(refreshToken()).rejects.toThrow('Network Error');
|
|
|
|
|
|
|
|
|
|
// Verify tokens were cleared on error
|
|
|
|
|
expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should update tokens with new values from API response', async () => {
|
|
|
|
|
const mockRefreshToken = 'old-refresh-token';
|
|
|
|
|
const newAccessToken = 'updated-access-token';
|
|
|
|
|
const newRefreshToken = 'updated-refresh-token';
|
|
|
|
|
|
|
|
|
|
// Mock TokenStorage.getRefreshToken
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);
|
|
|
|
|
|
|
|
|
|
mockPost.mockResolvedValue({
|
|
|
|
|
data: {
|
|
|
|
|
access_token: newAccessToken,
|
|
|
|
|
refresh_token: newRefreshToken,
|
|
|
|
|
expires_in: 900,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await refreshToken();
|
|
|
|
|
|
|
|
|
|
// Verify tokens were updated with new values
|
|
|
|
|
expect(mockTokenStorage.setTokens).toHaveBeenCalledWith(
|
|
|
|
|
newAccessToken,
|
2025-12-13 02:34:34 +00:00
|
|
|
newRefreshToken,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle empty refresh token string', async () => {
|
|
|
|
|
// Mock TokenStorage.getRefreshToken to return empty string
|
|
|
|
|
mockTokenStorage.getRefreshToken.mockReturnValue('');
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
await expect(refreshToken()).rejects.toThrow(
|
|
|
|
|
'No refresh token available',
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
// Verify API was not called
|
|
|
|
|
expect(mockPost).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|