veza/apps/web/src/services/tokenStorage.test.ts
senke 37981c2c17 chore(refactor/sumi-migration): commit pending changes — tests, stream server, dist_verification
- apps/web: test updates (Vitest/setup), playbackAnalyticsService, TrackGrid, serviceErrorHandler
- veza-common: logging, metrics, traits, validation, random
- veza-stream-server: audio pipeline, codecs, cache, monitoring, routes
- apps/web/dist_verification: refresh build assets (content-hashed filenames)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 19:39:18 +01:00

107 lines
3.3 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();
_resetTokenMemory();
});
describe('setTokens', () => {
it('should clean up legacy localStorage tokens (no-op for httpOnly cookies)', () => {
// SECURITY: Action 5.1.1.2 - setTokens is now a no-op that only cleans legacy localStorage
TokenStorage.setTokens('access-token', 'refresh-token');
// Should remove legacy localStorage entries, not set them
expect(localStorageMock.removeItem).toHaveBeenCalledWith(
'veza_access_token',
);
expect(localStorageMock.removeItem).toHaveBeenCalledWith(
'veza_refresh_token',
);
});
});
describe('getAccessToken', () => {
it('should return null (token is in httpOnly cookie, not accessible from JS)', () => {
const result = TokenStorage.getAccessToken();
expect(result).toBeNull();
});
});
describe('getRefreshToken', () => {
it('should return null (token is in httpOnly cookie, not accessible from JS)', () => {
const result = TokenStorage.getRefreshToken();
expect(result).toBeNull();
});
});
describe('clearTokens', () => {
it('should remove legacy localStorage tokens', () => {
TokenStorage.clearTokens();
expect(localStorageMock.removeItem).toHaveBeenCalledWith(
'veza_access_token',
);
expect(localStorageMock.removeItem).toHaveBeenCalledWith(
'veza_refresh_token',
);
});
it('should not throw error', () => {
expect(() => TokenStorage.clearTokens()).not.toThrow();
});
});
describe('hasTokens', () => {
it('should return false (cannot check httpOnly cookies from JavaScript)', () => {
const result = TokenStorage.hasTokens();
expect(result).toBe(false);
});
});
describe('integration', () => {
it('should work correctly with httpOnly cookie model', () => {
// With httpOnly cookies, JS cannot access tokens
expect(TokenStorage.hasTokens()).toBe(false);
expect(TokenStorage.getAccessToken()).toBeNull();
expect(TokenStorage.getRefreshToken()).toBeNull();
// setTokens is a no-op (just clears legacy localStorage)
TokenStorage.setTokens('access-123', 'refresh-456');
expect(TokenStorage.hasTokens()).toBe(false);
expect(TokenStorage.getAccessToken()).toBeNull();
expect(TokenStorage.getRefreshToken()).toBeNull();
// clearTokens cleans up legacy localStorage
TokenStorage.clearTokens();
expect(TokenStorage.hasTokens()).toBe(false);
expect(TokenStorage.getAccessToken()).toBeNull();
expect(TokenStorage.getRefreshToken()).toBeNull();
});
});
});