several fixes to various tests
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 14m14s

This commit is contained in:
2025-12-26 23:37:39 -08:00
parent c4742959e4
commit 768d02b9ed
20 changed files with 1113 additions and 63 deletions

View File

@@ -6,24 +6,28 @@ import { useAuth } from './useAuth';
import { AuthProvider } from '../providers/AuthProvider';
import * as apiClient from '../services/apiClient';
import type { UserProfile } from '../types';
import * as tokenStorage from '../services/tokenStorage';
import { createMockUserProfile } from '../tests/utils/mockFactories';
import { logger } from '../services/logger.client';
// Mock the dependencies
vi.mock('../services/apiClient', () => ({
// Mock other functions if needed
getAuthenticatedUserProfile: vi.fn(),
}));
vi.mock('../services/tokenStorage');
// Mock the logger to see auth provider logs during test execution
// Mock the logger to spy on its methods
vi.mock('../services/logger.client', () => ({
logger: {
info: vi.fn((...args) => console.log('[AUTH-INFO]', ...args)),
warn: vi.fn((...args) => console.warn('[AUTH-WARN]', ...args)),
error: vi.fn((...args) => console.error('[AUTH-ERROR]', ...args)),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
}));
const mockedApiClient = vi.mocked(apiClient);
const mockedTokenStorage = vi.mocked(tokenStorage);
const mockProfile: UserProfile = createMockUserProfile({
full_name: 'Test User',
@@ -36,26 +40,9 @@ const mockProfile: UserProfile = createMockUserProfile({
const wrapper = ({ children }: { children: ReactNode }) => <AuthProvider>{children}</AuthProvider>;
describe('useAuth Hook and AuthProvider', () => {
// Mock localStorage
let storage: { [key: string]: string } = {};
const localStorageMock = {
getItem: vi.fn((key: string) => storage[key] || null),
setItem: vi.fn((key: string, value: string) => {
storage[key] = value;
}),
removeItem: vi.fn((key: string) => {
delete storage[key];
}),
clear: vi.fn(() => {
storage = {};
}),
};
beforeEach(() => {
// Reset mocks and storage before each test
vi.clearAllMocks();
storage = {};
Object.defineProperty(window, 'localStorage', { value: localStorageMock, configurable: true });
});
afterEach(() => {
@@ -85,7 +72,8 @@ describe('useAuth Hook and AuthProvider', () => {
});
describe('Initial Auth Check (useEffect)', () => {
it('sets state to SIGNED_OUT if no token is found', async () => {
it('sets state to SIGNED_OUT if no token is found in storage', async () => {
mockedTokenStorage.getToken.mockReturnValue(null);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => {
@@ -97,7 +85,7 @@ describe('useAuth Hook and AuthProvider', () => {
});
it('sets state to AUTHENTICATED if a valid token is found', async () => {
localStorageMock.setItem('authToken', 'valid-token');
mockedTokenStorage.getToken.mockReturnValue('valid-token');
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue({
ok: true,
status: 200,
@@ -121,7 +109,7 @@ describe('useAuth Hook and AuthProvider', () => {
});
it('sets state to SIGNED_OUT and removes token if validation fails', async () => {
localStorageMock.setItem('authToken', 'invalid-token');
mockedTokenStorage.getToken.mockReturnValue('invalid-token');
mockedApiClient.getAuthenticatedUserProfile.mockRejectedValue(new Error('Invalid token'));
const { result } = renderHook(() => useAuth(), { wrapper });
@@ -132,10 +120,33 @@ describe('useAuth Hook and AuthProvider', () => {
expect(result.current.authStatus).toBe('SIGNED_OUT');
expect(result.current.userProfile).toBeNull();
expect(localStorageMock.removeItem).toHaveBeenCalledWith('authToken');
expect(mockedTokenStorage.removeToken).toHaveBeenCalled();
});
});
it('sets state to SIGNED_OUT and removes token if profile fetch returns null after token validation', async () => {
mockedTokenStorage.getToken.mockReturnValue('valid-token');
// Mock getAuthenticatedUserProfile to return a 200 OK response with a null body
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve(null), // Simulate API returning no profile data
} as unknown as Response);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.authStatus).toBe('SIGNED_OUT');
expect(result.current.userProfile).toBeNull();
expect(mockedTokenStorage.removeToken).toHaveBeenCalled();
expect(logger.warn).toHaveBeenCalledWith(
'[AuthProvider-Effect] Token was present but validation returned no profile. Signing out.',
);
});
describe('login function', () => {
// This was the failing test
it('sets token, fetches profile, and updates state on successful login', async () => {
@@ -172,7 +183,7 @@ describe('useAuth Hook and AuthProvider', () => {
console.log('[TEST-DEBUG] State immediately after login `act` call:', result.current);
// 3. Assertions
expect(localStorageMock.setItem).toHaveBeenCalledWith('authToken', 'new-valid-token');
expect(mockedTokenStorage.setToken).toHaveBeenCalledWith('new-valid-token');
// 4. We must wait for the state update inside the hook to propagate
await waitFor(() => {
@@ -202,16 +213,44 @@ describe('useAuth Hook and AuthProvider', () => {
});
// Should trigger the logout flow
expect(localStorageMock.removeItem).toHaveBeenCalledWith('authToken');
expect(mockedTokenStorage.removeToken).toHaveBeenCalled();
expect(result.current.authStatus).toBe('SIGNED_OUT'); // This was a duplicate, fixed.
expect(result.current.userProfile).toBeNull();
});
it('logs out and throws an error if profile fetch returns null after login (no profileData)', async () => {
// Simulate successful token setting, but subsequent profile fetch returns null
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue({
ok: true,
status: 200,
json: () => Promise.resolve(null), // Simulate API returning no profile data
} as unknown as Response);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.isLoading).toBe(false));
// Call login without profileData, forcing a profile fetch
await act(async () => {
await expect(result.current.login('new-token-no-profile-data')).rejects.toThrow(
'Login succeeded, but failed to fetch your data: Received null or undefined profile from API.',
);
});
// Should trigger the logout flow
expect(mockedTokenStorage.removeToken).toHaveBeenCalled();
expect(result.current.authStatus).toBe('SIGNED_OUT');
expect(result.current.userProfile).toBeNull();
expect(logger.error).toHaveBeenCalledWith(
expect.any(String), // The error message
expect.objectContaining({ error: 'Received null or undefined profile from API.' }),
);
});
});
describe('logout function', () => {
it('removes token and resets auth state', async () => {
// Start in a logged-in state
localStorageMock.setItem('authToken', 'valid-token');
// Start in a logged-in state by mocking the token storage
mockedTokenStorage.getToken.mockReturnValue('valid-token');
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue({
ok: true,
status: 200,
@@ -227,16 +266,15 @@ describe('useAuth Hook and AuthProvider', () => {
result.current.logout();
});
expect(localStorageMock.removeItem).toHaveBeenCalledWith('authToken');
expect(mockedTokenStorage.removeToken).toHaveBeenCalled();
expect(result.current.authStatus).toBe('SIGNED_OUT');
expect(result.current.userProfile).toBeNull();
});
});
describe('updateProfile function', () => {
it('merges new data into the existing profile state', async () => {
// Start in a logged-in state
localStorageMock.setItem('authToken', 'valid-token');
it('merges new data into the existing profile state', async () => { // Start in a logged-in state
mockedTokenStorage.getToken.mockReturnValue('valid-token');
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue({
ok: true,
status: 200,