194 lines
5.8 KiB
TypeScript
194 lines
5.8 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { TokenStorage, _resetTokenMemory } from './tokenStorage';
|
|
|
|
// Mock localStorage
|
|
const localStorageMock = (() => {
|
|
let store: Record<string, string> = {};
|
|
|
|
return {
|
|
getItem: vi.fn((key: string) => store[key] || null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
store[key] = value.toString();
|
|
}),
|
|
removeItem: vi.fn((key: string) => {
|
|
delete store[key];
|
|
}),
|
|
clear: vi.fn(() => {
|
|
store = {};
|
|
}),
|
|
};
|
|
})();
|
|
|
|
Object.defineProperty(window, 'localStorage', {
|
|
value: localStorageMock,
|
|
writable: true,
|
|
});
|
|
|
|
describe('TokenStorage', () => {
|
|
beforeEach(() => {
|
|
localStorageMock.clear();
|
|
vi.clearAllMocks();
|
|
// SECURITY: Réinitialiser la mémoire pour chaque test
|
|
_resetTokenMemory();
|
|
});
|
|
|
|
describe('setTokens', () => {
|
|
it('should store access token and refresh token in localStorage', () => {
|
|
const accessToken = 'test-access-token';
|
|
const refreshToken = 'test-refresh-token';
|
|
|
|
TokenStorage.setTokens(accessToken, refreshToken);
|
|
|
|
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
|
'veza_access_token',
|
|
accessToken,
|
|
);
|
|
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
|
'veza_refresh_token',
|
|
refreshToken,
|
|
);
|
|
expect(localStorageMock.setItem).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should overwrite existing tokens when called multiple times', () => {
|
|
TokenStorage.setTokens('token1', 'refresh1');
|
|
TokenStorage.setTokens('token2', 'refresh2');
|
|
|
|
expect(localStorageMock.setItem).toHaveBeenCalledTimes(4);
|
|
// Check that the last two calls set the new tokens
|
|
const calls = localStorageMock.setItem.mock.calls;
|
|
expect(calls[2]).toEqual(['veza_access_token', 'token2']);
|
|
expect(calls[3]).toEqual(['veza_refresh_token', 'refresh2']);
|
|
});
|
|
});
|
|
|
|
describe('getAccessToken', () => {
|
|
it('should return access token from localStorage', () => {
|
|
const accessToken = 'test-access-token';
|
|
localStorageMock.setItem('veza_access_token', accessToken);
|
|
|
|
const result = TokenStorage.getAccessToken();
|
|
|
|
expect(result).toBe(accessToken);
|
|
expect(localStorageMock.getItem).toHaveBeenCalledWith(
|
|
'veza_access_token',
|
|
);
|
|
});
|
|
|
|
it('should return null if access token does not exist', () => {
|
|
const result = TokenStorage.getAccessToken();
|
|
|
|
expect(result).toBeNull();
|
|
expect(localStorageMock.getItem).toHaveBeenCalledWith(
|
|
'veza_access_token',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getRefreshToken', () => {
|
|
it('should return refresh token from localStorage', () => {
|
|
const refreshToken = 'test-refresh-token';
|
|
localStorageMock.setItem('veza_refresh_token', refreshToken);
|
|
|
|
const result = TokenStorage.getRefreshToken();
|
|
|
|
expect(result).toBe(refreshToken);
|
|
expect(localStorageMock.getItem).toHaveBeenCalledWith(
|
|
'veza_refresh_token',
|
|
);
|
|
});
|
|
|
|
it('should return null if refresh token does not exist', () => {
|
|
const result = TokenStorage.getRefreshToken();
|
|
|
|
expect(result).toBeNull();
|
|
expect(localStorageMock.getItem).toHaveBeenCalledWith(
|
|
'veza_refresh_token',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('clearTokens', () => {
|
|
it('should remove both tokens from localStorage', () => {
|
|
TokenStorage.setTokens('access-token', 'refresh-token');
|
|
localStorageMock.setItem.mockClear();
|
|
|
|
TokenStorage.clearTokens();
|
|
|
|
expect(localStorageMock.removeItem).toHaveBeenCalledWith(
|
|
'veza_access_token',
|
|
);
|
|
expect(localStorageMock.removeItem).toHaveBeenCalledWith(
|
|
'veza_refresh_token',
|
|
);
|
|
expect(localStorageMock.removeItem).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should not throw error if tokens do not exist', () => {
|
|
expect(() => TokenStorage.clearTokens()).not.toThrow();
|
|
expect(localStorageMock.removeItem).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe('hasTokens', () => {
|
|
it('should return true when both tokens exist', () => {
|
|
TokenStorage.setTokens('access-token', 'refresh-token');
|
|
|
|
const result = TokenStorage.hasTokens();
|
|
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false when access token is missing', () => {
|
|
localStorageMock.setItem('veza_refresh_token', 'refresh-token');
|
|
|
|
const result = TokenStorage.hasTokens();
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should return false when refresh token is missing', () => {
|
|
localStorageMock.setItem('veza_access_token', 'access-token');
|
|
|
|
const result = TokenStorage.hasTokens();
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should return false when both tokens are missing', () => {
|
|
const result = TokenStorage.hasTokens();
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should return false when tokens are empty strings', () => {
|
|
localStorageMock.setItem('veza_access_token', '');
|
|
localStorageMock.setItem('veza_refresh_token', '');
|
|
|
|
const result = TokenStorage.hasTokens();
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('integration', () => {
|
|
it('should work correctly with full token lifecycle', () => {
|
|
// Initially no tokens
|
|
expect(TokenStorage.hasTokens()).toBe(false);
|
|
expect(TokenStorage.getAccessToken()).toBeNull();
|
|
expect(TokenStorage.getRefreshToken()).toBeNull();
|
|
|
|
// Store tokens
|
|
TokenStorage.setTokens('access-123', 'refresh-456');
|
|
expect(TokenStorage.hasTokens()).toBe(true);
|
|
expect(TokenStorage.getAccessToken()).toBe('access-123');
|
|
expect(TokenStorage.getRefreshToken()).toBe('refresh-456');
|
|
|
|
// Clear tokens
|
|
TokenStorage.clearTokens();
|
|
expect(TokenStorage.hasTokens()).toBe(false);
|
|
expect(TokenStorage.getAccessToken()).toBeNull();
|
|
expect(TokenStorage.getRefreshToken()).toBeNull();
|
|
});
|
|
});
|
|
});
|