imageFilenames and imageFilename, backend integration tests, frontend tests, removed username references

This commit is contained in:
jackiettran
2025-11-26 23:13:23 -05:00
parent f2d3aac029
commit 11593606aa
52 changed files with 2815 additions and 150 deletions

View File

@@ -0,0 +1,461 @@
import React from 'react';
import { render, screen, waitFor, act, fireEvent } from '@testing-library/react';
import { AuthProvider, useAuth } from '../../contexts/AuthContext';
import { mockUser } from '../../mocks/handlers';
// Mock the API module
jest.mock('../../services/api', () => {
const mockAuthAPI = {
login: jest.fn(),
register: jest.fn(),
googleLogin: jest.fn(),
logout: jest.fn(),
getStatus: jest.fn(),
};
const mockFetchCSRFToken = jest.fn().mockResolvedValue('test-csrf-token');
const mockResetCSRFToken = jest.fn();
return {
authAPI: mockAuthAPI,
fetchCSRFToken: mockFetchCSRFToken,
resetCSRFToken: mockResetCSRFToken,
};
});
// Get mocked modules
import { authAPI, fetchCSRFToken, resetCSRFToken } from '../../services/api';
const mockAuthAPI = authAPI as jest.Mocked<typeof authAPI>;
const mockFetchCSRFToken = fetchCSRFToken as jest.MockedFunction<typeof fetchCSRFToken>;
const mockResetCSRFToken = resetCSRFToken as jest.MockedFunction<typeof resetCSRFToken>;
// Test component that uses the auth context
const TestComponent: React.FC = () => {
const auth = useAuth();
return (
<div>
<div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
<div data-testid="user">{auth.user ? auth.user.email : 'no-user'}</div>
<div data-testid="verified">{auth.user?.isVerified ? 'verified' : 'not-verified'}</div>
<div data-testid="modal-open">{auth.showAuthModal ? 'open' : 'closed'}</div>
<div data-testid="modal-mode">{auth.authModalMode}</div>
<button onClick={() => auth.login('test@example.com', 'password123')}>Login</button>
<button onClick={() => auth.register({ email: 'new@example.com', username: 'newuser', password: 'password123' })}>Register</button>
<button onClick={() => auth.googleLogin('valid-google-code')}>Google Login</button>
<button onClick={() => auth.logout()}>Logout</button>
<button onClick={() => auth.openAuthModal('login')}>Open Login Modal</button>
<button onClick={() => auth.openAuthModal('signup')}>Open Signup Modal</button>
<button onClick={() => auth.closeAuthModal()}>Close Modal</button>
<button onClick={() => auth.checkAuth()}>Check Auth</button>
</div>
);
};
// Wrapper component for testing
const renderWithAuth = (ui: React.ReactElement = <TestComponent />) => {
return render(
<AuthProvider>
{ui}
</AuthProvider>
);
};
describe('AuthContext', () => {
beforeEach(() => {
jest.clearAllMocks();
// Default: user is authenticated
mockAuthAPI.getStatus.mockResolvedValue({
data: { authenticated: true, user: mockUser },
});
mockFetchCSRFToken.mockResolvedValue('test-csrf-token');
});
describe('useAuth hook', () => {
it('throws error when used outside AuthProvider', () => {
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
expect(() => render(<TestComponent />)).toThrow('useAuth must be used within an AuthProvider');
consoleError.mockRestore();
});
});
describe('Initial State', () => {
it('starts with loading state', () => {
renderWithAuth();
expect(screen.getByTestId('loading')).toHaveTextContent('loading');
});
it('checks authentication status on mount', async () => {
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
expect(mockAuthAPI.getStatus).toHaveBeenCalled();
});
it('sets user to null when not authenticated', async () => {
mockAuthAPI.getStatus.mockResolvedValue({
data: { authenticated: false, user: null },
});
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
});
it('handles network errors gracefully', async () => {
mockAuthAPI.getStatus.mockRejectedValue(new Error('Network error'));
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
});
});
describe('Login', () => {
it('logs in successfully with valid credentials', async () => {
mockAuthAPI.getStatus.mockResolvedValue({
data: { authenticated: false, user: null },
});
mockAuthAPI.login.mockResolvedValue({
data: { user: mockUser },
});
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
await act(async () => {
fireEvent.click(screen.getByText('Login'));
});
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
});
expect(mockAuthAPI.login).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
it('keeps user as null when login fails', async () => {
mockAuthAPI.getStatus.mockResolvedValue({
data: { authenticated: false, user: null },
});
mockAuthAPI.login.mockRejectedValue(new Error('Invalid credentials'));
// Create a test component that captures login errors
const LoginErrorTestComponent: React.FC = () => {
const auth = useAuth();
const [error, setError] = React.useState<string | null>(null);
const handleLogin = async () => {
try {
await auth.login('test@example.com', 'wrongpassword');
} catch (err: any) {
setError(err.message);
}
};
return (
<div>
<div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
<div data-testid="user">{auth.user ? auth.user.email : 'no-user'}</div>
<div data-testid="error">{error || 'no-error'}</div>
<button onClick={handleLogin}>Login</button>
</div>
);
};
render(
<AuthProvider>
<LoginErrorTestComponent />
</AuthProvider>
);
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
await act(async () => {
fireEvent.click(screen.getByText('Login'));
});
await waitFor(() => {
expect(screen.getByTestId('error')).toHaveTextContent('Invalid credentials');
});
// User should still be null after failed login
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
});
});
describe('Registration', () => {
it('registers a new user successfully', async () => {
mockAuthAPI.getStatus.mockResolvedValue({
data: { authenticated: false, user: null },
});
mockAuthAPI.register.mockResolvedValue({
data: { user: { ...mockUser, email: 'new@example.com', isVerified: false } },
});
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
await act(async () => {
fireEvent.click(screen.getByText('Register'));
});
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('new@example.com');
});
});
});
describe('Google Login', () => {
it('logs in with Google successfully', async () => {
mockAuthAPI.getStatus.mockResolvedValue({
data: { authenticated: false, user: null },
});
mockAuthAPI.googleLogin.mockResolvedValue({
data: { user: mockUser },
});
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
await act(async () => {
fireEvent.click(screen.getByText('Google Login'));
});
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
});
expect(mockAuthAPI.googleLogin).toHaveBeenCalledWith('valid-google-code');
});
});
describe('Logout', () => {
it('logs out successfully', async () => {
mockAuthAPI.logout.mockResolvedValue({ data: { message: 'Logged out' } });
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
});
await act(async () => {
fireEvent.click(screen.getByText('Logout'));
});
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
});
expect(mockResetCSRFToken).toHaveBeenCalled();
});
it('clears user state even if logout API fails', async () => {
mockAuthAPI.logout.mockRejectedValue(new Error('Server error'));
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
});
await act(async () => {
fireEvent.click(screen.getByText('Logout'));
});
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
});
});
});
describe('Auth Modal', () => {
it('opens login modal', async () => {
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('modal-open')).toHaveTextContent('closed');
await act(async () => {
fireEvent.click(screen.getByText('Open Login Modal'));
});
expect(screen.getByTestId('modal-open')).toHaveTextContent('open');
expect(screen.getByTestId('modal-mode')).toHaveTextContent('login');
});
it('opens signup modal', async () => {
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
await act(async () => {
fireEvent.click(screen.getByText('Open Signup Modal'));
});
expect(screen.getByTestId('modal-open')).toHaveTextContent('open');
expect(screen.getByTestId('modal-mode')).toHaveTextContent('signup');
});
it('closes modal', async () => {
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
await act(async () => {
fireEvent.click(screen.getByText('Open Login Modal'));
});
expect(screen.getByTestId('modal-open')).toHaveTextContent('open');
await act(async () => {
fireEvent.click(screen.getByText('Close Modal'));
});
expect(screen.getByTestId('modal-open')).toHaveTextContent('closed');
});
});
describe('updateUser', () => {
it('updates user state', async () => {
const TestComponentWithUpdate: React.FC = () => {
const auth = useAuth();
return (
<div>
<div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
<div data-testid="user-email">{auth.user?.email || 'no-user'}</div>
<div data-testid="user-name">{auth.user?.firstName || 'no-name'}</div>
<button onClick={() => auth.updateUser({
...auth.user!,
firstName: 'Updated',
lastName: 'Name',
})}>
Update User
</button>
</div>
);
};
renderWithAuth(<TestComponentWithUpdate />);
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('user-name')).toHaveTextContent('Test');
await act(async () => {
fireEvent.click(screen.getByText('Update User'));
});
expect(screen.getByTestId('user-name')).toHaveTextContent('Updated');
});
});
describe('checkAuth', () => {
it('refreshes authentication status', async () => {
let callCount = 0;
mockAuthAPI.getStatus.mockImplementation(() => {
callCount++;
if (callCount === 1) {
return Promise.resolve({ data: { authenticated: false, user: null } });
}
return Promise.resolve({ data: { authenticated: true, user: mockUser } });
});
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
expect(screen.getByTestId('user')).toHaveTextContent('no-user');
await act(async () => {
fireEvent.click(screen.getByText('Check Auth'));
});
await waitFor(() => {
expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
});
});
});
describe('OAuth Callback Handling', () => {
it('skips auth check on OAuth callback page', async () => {
// Mock being on the OAuth callback page
Object.defineProperty(window, 'location', {
value: {
...window.location,
pathname: '/auth/google/callback',
},
writable: true,
});
mockAuthAPI.getStatus.mockClear();
renderWithAuth();
await waitFor(() => {
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
});
// Status should not be called on OAuth callback page
expect(mockAuthAPI.getStatus).not.toHaveBeenCalled();
// Reset location
Object.defineProperty(window, 'location', {
value: {
...window.location,
pathname: '/',
},
writable: true,
});
});
});
});

View File

@@ -0,0 +1,346 @@
import { renderHook } from '@testing-library/react';
import { useAddressAutocomplete, usStates } from '../../hooks/useAddressAutocomplete';
import { PlaceDetails } from '../../services/placesService';
describe('useAddressAutocomplete', () => {
describe('usStates', () => {
it('contains all 50 US states', () => {
expect(usStates).toHaveLength(50);
});
it('includes common states', () => {
expect(usStates).toContain('California');
expect(usStates).toContain('New York');
expect(usStates).toContain('Texas');
expect(usStates).toContain('Florida');
});
it('states are in alphabetical order', () => {
const sorted = [...usStates].sort();
expect(usStates).toEqual(sorted);
});
});
describe('parsePlace', () => {
const { result } = renderHook(() => useAddressAutocomplete());
it('parses a complete place correctly', () => {
const place: PlaceDetails = {
formattedAddress: '123 Main Street, Los Angeles, CA 90210, USA',
addressComponents: {
streetNumber: '123',
route: 'Main Street',
locality: 'Los Angeles',
administrativeAreaLevel1: 'CA',
administrativeAreaLevel1Long: 'California',
postalCode: '90210',
country: 'US',
},
geometry: {
latitude: 34.0522,
longitude: -118.2437,
},
placeId: 'test-place-id',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.address1).toBe('123 Main Street');
expect(parsed!.city).toBe('Los Angeles');
expect(parsed!.state).toBe('California');
expect(parsed!.zipCode).toBe('90210');
expect(parsed!.country).toBe('US');
expect(parsed!.latitude).toBe(34.0522);
expect(parsed!.longitude).toBe(-118.2437);
});
it('converts state codes to full names', () => {
const place: PlaceDetails = {
formattedAddress: '456 Oak Ave, New York, NY 10001, USA',
addressComponents: {
streetNumber: '456',
route: 'Oak Ave',
locality: 'New York',
administrativeAreaLevel1: 'NY',
administrativeAreaLevel1Long: 'New York',
postalCode: '10001',
country: 'US',
},
geometry: {
latitude: 40.7128,
longitude: -74.006,
},
placeId: 'test-place-id-2',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.state).toBe('New York');
});
it('uses formatted address when street number and route are missing', () => {
const place: PlaceDetails = {
formattedAddress: 'Some Place, Austin, TX 78701, USA',
addressComponents: {
locality: 'Austin',
administrativeAreaLevel1: 'TX',
administrativeAreaLevel1Long: 'Texas',
postalCode: '78701',
country: 'US',
},
geometry: {
latitude: 30.2672,
longitude: -97.7431,
},
placeId: 'test-place-id-3',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.address1).toBe('Some Place, Austin, TX 78701, USA');
});
it('handles missing postal code gracefully', () => {
const place: PlaceDetails = {
formattedAddress: '789 Pine St, Seattle, WA, USA',
addressComponents: {
streetNumber: '789',
route: 'Pine St',
locality: 'Seattle',
administrativeAreaLevel1: 'WA',
administrativeAreaLevel1Long: 'Washington',
country: 'US',
},
geometry: {
latitude: 47.6062,
longitude: -122.3321,
},
placeId: 'test-place-id-4',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.zipCode).toBe('');
expect(parsed!.state).toBe('Washington');
});
it('handles missing city gracefully', () => {
const place: PlaceDetails = {
formattedAddress: '100 Rural Road, CO 80000, USA',
addressComponents: {
streetNumber: '100',
route: 'Rural Road',
administrativeAreaLevel1: 'CO',
administrativeAreaLevel1Long: 'Colorado',
postalCode: '80000',
country: 'US',
},
geometry: {
latitude: 39.5501,
longitude: -105.7821,
},
placeId: 'test-place-id-5',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.city).toBe('');
});
it('sets state to empty string for unknown states', () => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
const place: PlaceDetails = {
formattedAddress: '123 Street, City, XX 12345, USA',
addressComponents: {
streetNumber: '123',
route: 'Street',
locality: 'City',
administrativeAreaLevel1: 'XX',
administrativeAreaLevel1Long: 'Unknown State',
postalCode: '12345',
country: 'US',
},
geometry: {
latitude: 0,
longitude: 0,
},
placeId: 'test-place-id-6',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.state).toBe('');
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('State not found in dropdown options')
);
consoleSpy.mockRestore();
});
it('handles DC (District of Columbia) correctly', () => {
const place: PlaceDetails = {
formattedAddress: '1600 Pennsylvania Ave, Washington, DC 20500, USA',
addressComponents: {
streetNumber: '1600',
route: 'Pennsylvania Ave',
locality: 'Washington',
administrativeAreaLevel1: 'DC',
administrativeAreaLevel1Long: 'District of Columbia',
postalCode: '20500',
country: 'US',
},
geometry: {
latitude: 38.8977,
longitude: -77.0365,
},
placeId: 'test-place-id-dc',
};
const parsed = result.current.parsePlace(place);
// DC is not in the 50 states list, so state should be empty
expect(parsed).not.toBeNull();
expect(parsed!.city).toBe('Washington');
});
it('uses long state name from administrativeAreaLevel1Long when available', () => {
const place: PlaceDetails = {
formattedAddress: '500 Beach Blvd, Miami, FL 33101, USA',
addressComponents: {
streetNumber: '500',
route: 'Beach Blvd',
locality: 'Miami',
administrativeAreaLevel1: 'FL',
administrativeAreaLevel1Long: 'Florida',
postalCode: '33101',
country: 'US',
},
geometry: {
latitude: 25.7617,
longitude: -80.1918,
},
placeId: 'test-place-id-fl',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.state).toBe('Florida');
});
it('returns null and logs error when parsing fails', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
// Pass an object that will cause an error when accessing nested properties
const invalidPlace = {
formattedAddress: 'Test',
addressComponents: null,
geometry: { latitude: 0, longitude: 0 },
placeId: 'test',
} as unknown as PlaceDetails;
const parsed = result.current.parsePlace(invalidPlace);
expect(parsed).toBeNull();
expect(consoleSpy).toHaveBeenCalledWith(
'Error parsing place details:',
expect.any(Error)
);
consoleSpy.mockRestore();
});
it('handles only street number without route', () => {
const place: PlaceDetails = {
formattedAddress: '42, Chicago, IL 60601, USA',
addressComponents: {
streetNumber: '42',
locality: 'Chicago',
administrativeAreaLevel1: 'IL',
administrativeAreaLevel1Long: 'Illinois',
postalCode: '60601',
country: 'US',
},
geometry: {
latitude: 41.8781,
longitude: -87.6298,
},
placeId: 'test-place-id-number-only',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.address1).toBe('42');
});
it('handles only route without street number', () => {
const place: PlaceDetails = {
formattedAddress: 'Main Street, Boston, MA 02101, USA',
addressComponents: {
route: 'Main Street',
locality: 'Boston',
administrativeAreaLevel1: 'MA',
administrativeAreaLevel1Long: 'Massachusetts',
postalCode: '02101',
country: 'US',
},
geometry: {
latitude: 42.3601,
longitude: -71.0589,
},
placeId: 'test-place-id-route-only',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.address1).toBe('Main Street');
});
it('defaults country to US when not provided', () => {
const place: PlaceDetails = {
formattedAddress: '999 Test St, Denver, CO 80202',
addressComponents: {
streetNumber: '999',
route: 'Test St',
locality: 'Denver',
administrativeAreaLevel1: 'CO',
administrativeAreaLevel1Long: 'Colorado',
postalCode: '80202',
},
geometry: {
latitude: 39.7392,
longitude: -104.9903,
},
placeId: 'test-place-id-no-country',
};
const parsed = result.current.parsePlace(place);
expect(parsed).not.toBeNull();
expect(parsed!.country).toBe('US');
});
});
describe('hook stability', () => {
it('returns stable parsePlace function', () => {
const { result, rerender } = renderHook(() => useAddressAutocomplete());
const firstParsePlace = result.current.parsePlace;
rerender();
const secondParsePlace = result.current.parsePlace;
expect(firstParsePlace).toBe(secondParsePlace);
});
});
});

View File

@@ -0,0 +1,211 @@
/**
* API Service Tests
*
* Tests the API service module structure and exported functions.
* API interceptor behavior is tested in integration with AuthContext.
*/
import {
authAPI,
userAPI,
itemAPI,
rentalAPI,
messageAPI,
mapsAPI,
stripeAPI,
addressAPI,
conditionCheckAPI,
forumAPI,
feedbackAPI,
fetchCSRFToken,
resetCSRFToken,
getMessageImageUrl,
getForumImageUrl,
} from '../../services/api';
import api from '../../services/api';
describe('API Service', () => {
describe('Module Exports', () => {
it('exports authAPI with correct methods', () => {
expect(authAPI).toBeDefined();
expect(typeof authAPI.login).toBe('function');
expect(typeof authAPI.register).toBe('function');
expect(typeof authAPI.logout).toBe('function');
expect(typeof authAPI.googleLogin).toBe('function');
expect(typeof authAPI.getStatus).toBe('function');
expect(typeof authAPI.verifyEmail).toBe('function');
expect(typeof authAPI.forgotPassword).toBe('function');
expect(typeof authAPI.resetPassword).toBe('function');
});
it('exports userAPI with correct methods', () => {
expect(userAPI).toBeDefined();
expect(typeof userAPI.getProfile).toBe('function');
expect(typeof userAPI.updateProfile).toBe('function');
expect(typeof userAPI.uploadProfileImage).toBe('function');
});
it('exports itemAPI with correct methods', () => {
expect(itemAPI).toBeDefined();
expect(typeof itemAPI.getItems).toBe('function');
expect(typeof itemAPI.getItem).toBe('function');
expect(typeof itemAPI.createItem).toBe('function');
expect(typeof itemAPI.updateItem).toBe('function');
expect(typeof itemAPI.deleteItem).toBe('function');
});
it('exports rentalAPI with correct methods', () => {
expect(rentalAPI).toBeDefined();
expect(typeof rentalAPI.createRental).toBe('function');
expect(typeof rentalAPI.getRentals).toBe('function');
expect(typeof rentalAPI.getListings).toBe('function');
expect(typeof rentalAPI.updateRentalStatus).toBe('function');
expect(typeof rentalAPI.cancelRental).toBe('function');
});
it('exports messageAPI with correct methods', () => {
expect(messageAPI).toBeDefined();
expect(typeof messageAPI.getMessages).toBe('function');
expect(typeof messageAPI.getConversations).toBe('function');
expect(typeof messageAPI.sendMessage).toBe('function');
expect(typeof messageAPI.getUnreadCount).toBe('function');
});
it('exports mapsAPI with correct methods', () => {
expect(mapsAPI).toBeDefined();
expect(typeof mapsAPI.placesAutocomplete).toBe('function');
expect(typeof mapsAPI.placeDetails).toBe('function');
expect(typeof mapsAPI.geocode).toBe('function');
});
it('exports stripeAPI with correct methods', () => {
expect(stripeAPI).toBeDefined();
expect(typeof stripeAPI.getCheckoutSession).toBe('function');
expect(typeof stripeAPI.createConnectedAccount).toBe('function');
expect(typeof stripeAPI.createAccountLink).toBe('function');
expect(typeof stripeAPI.getAccountStatus).toBe('function');
});
it('exports CSRF token management functions', () => {
expect(typeof fetchCSRFToken).toBe('function');
expect(typeof resetCSRFToken).toBe('function');
});
it('exports helper functions for image URLs', () => {
expect(typeof getMessageImageUrl).toBe('function');
expect(typeof getForumImageUrl).toBe('function');
});
});
describe('Helper Functions', () => {
it('getMessageImageUrl constructs correct URL', () => {
const url = getMessageImageUrl('test-image.jpg');
expect(url).toContain('/messages/images/test-image.jpg');
});
it('getForumImageUrl constructs correct URL', () => {
const url = getForumImageUrl('forum-image.jpg');
expect(url).toContain('/uploads/forum/forum-image.jpg');
});
});
describe('CSRF Token Management', () => {
it('resetCSRFToken clears the token', () => {
// Should not throw
expect(() => resetCSRFToken()).not.toThrow();
});
});
describe('API Configuration', () => {
it('creates axios instance with correct base URL', () => {
expect(api).toBeDefined();
expect(api.defaults).toBeDefined();
});
});
});
describe('API Namespaces', () => {
describe('authAPI', () => {
it('has all authentication methods', () => {
const expectedMethods = [
'register',
'login',
'googleLogin',
'logout',
'refresh',
'getCSRFToken',
'getStatus',
'verifyEmail',
'resendVerification',
'forgotPassword',
'verifyResetToken',
'resetPassword',
];
expectedMethods.forEach((method) => {
expect((authAPI as any)[method]).toBeDefined();
expect(typeof (authAPI as any)[method]).toBe('function');
});
});
});
describe('addressAPI', () => {
it('has all address management methods', () => {
const expectedMethods = [
'getAddresses',
'createAddress',
'updateAddress',
'deleteAddress',
];
expectedMethods.forEach((method) => {
expect((addressAPI as any)[method]).toBeDefined();
expect(typeof (addressAPI as any)[method]).toBe('function');
});
});
});
describe('conditionCheckAPI', () => {
it('has all condition check methods', () => {
const expectedMethods = [
'submitConditionCheck',
'getConditionChecks',
'getConditionCheckTimeline',
'getAvailableChecks',
];
expectedMethods.forEach((method) => {
expect((conditionCheckAPI as any)[method]).toBeDefined();
expect(typeof (conditionCheckAPI as any)[method]).toBe('function');
});
});
});
describe('forumAPI', () => {
it('has all forum methods', () => {
const expectedMethods = [
'getPosts',
'getPost',
'createPost',
'updatePost',
'deletePost',
'createComment',
'updateComment',
'deleteComment',
'getTags',
];
expectedMethods.forEach((method) => {
expect((forumAPI as any)[method]).toBeDefined();
expect(typeof (forumAPI as any)[method]).toBe('function');
});
});
});
describe('feedbackAPI', () => {
it('has feedback submission method', () => {
expect(feedbackAPI.submitFeedback).toBeDefined();
expect(typeof feedbackAPI.submitFeedback).toBe('function');
});
});
});