Add comprehensive tests verifying the web API client uses the same WellNuo backend (wellnuo.smartlaunchhub.com/api) as the mobile app. Tests cover beneficiaries CRUD, profile operations, and auth handling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
371 lines
10 KiB
TypeScript
371 lines
10 KiB
TypeScript
import { api } from '@/lib/api';
|
|
|
|
// Mock localStorage
|
|
const localStorageMock = (() => {
|
|
let store: Record<string, string> = {};
|
|
|
|
return {
|
|
getItem: (key: string) => store[key] || null,
|
|
setItem: (key: string, value: string) => {
|
|
store[key] = value.toString();
|
|
},
|
|
removeItem: (key: string) => {
|
|
delete store[key];
|
|
},
|
|
clear: () => {
|
|
store = {};
|
|
},
|
|
};
|
|
})();
|
|
|
|
Object.defineProperty(window, 'localStorage', {
|
|
value: localStorageMock,
|
|
});
|
|
|
|
// Mock crypto.getRandomValues
|
|
Object.defineProperty(global, 'crypto', {
|
|
value: {
|
|
getRandomValues: (arr: Uint8Array) => {
|
|
for (let i = 0; i < arr.length; i++) {
|
|
arr[i] = Math.floor(Math.random() * 256);
|
|
}
|
|
return arr;
|
|
},
|
|
},
|
|
});
|
|
|
|
describe('API Service', () => {
|
|
beforeEach(() => {
|
|
localStorageMock.clear();
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('Token Management', () => {
|
|
it('should save and retrieve access token', async () => {
|
|
const testToken = 'test-jwt-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
const token = await api.getToken();
|
|
expect(token).toBe(testToken);
|
|
});
|
|
|
|
it('should return null when no token exists', async () => {
|
|
const token = await api.getToken();
|
|
expect(token).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Email Management', () => {
|
|
it('should save and retrieve email', async () => {
|
|
const testEmail = 'test@example.com';
|
|
await api.saveEmail(testEmail);
|
|
|
|
const email = await api.getStoredEmail();
|
|
expect(email).toBe(testEmail);
|
|
});
|
|
|
|
it('should return null when no email is stored', async () => {
|
|
const email = await api.getStoredEmail();
|
|
expect(email).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Onboarding Status', () => {
|
|
it('should save and retrieve onboarding completed flag', async () => {
|
|
await api.setOnboardingCompleted(true);
|
|
let isCompleted = await api.isOnboardingCompleted();
|
|
expect(isCompleted).toBe(true);
|
|
|
|
await api.setOnboardingCompleted(false);
|
|
isCompleted = await api.isOnboardingCompleted();
|
|
expect(isCompleted).toBe(false);
|
|
});
|
|
|
|
it('should return false when onboarding flag is not set', async () => {
|
|
const isCompleted = await api.isOnboardingCompleted();
|
|
expect(isCompleted).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Authentication Status', () => {
|
|
it('should return true when token exists', async () => {
|
|
localStorageMock.setItem('accessToken', 'test-token');
|
|
const isAuth = await api.isAuthenticated();
|
|
expect(isAuth).toBe(true);
|
|
});
|
|
|
|
it('should return false when token does not exist', async () => {
|
|
const isAuth = await api.isAuthenticated();
|
|
expect(isAuth).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Logout', () => {
|
|
it('should clear all stored data', async () => {
|
|
// Set up some data
|
|
localStorageMock.setItem('accessToken', 'token');
|
|
localStorageMock.setItem('userId', '123');
|
|
localStorageMock.setItem('userEmail', 'test@example.com');
|
|
localStorageMock.setItem('legacyAccessToken', 'legacy-token');
|
|
|
|
await api.logout();
|
|
|
|
// Verify all data is cleared
|
|
expect(localStorageMock.getItem('accessToken')).toBeNull();
|
|
expect(localStorageMock.getItem('userId')).toBeNull();
|
|
expect(localStorageMock.getItem('userEmail')).toBeNull();
|
|
expect(localStorageMock.getItem('legacyAccessToken')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Check Email', () => {
|
|
it('should call the API with correct endpoint', async () => {
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
json: () => Promise.resolve({ exists: true }),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.checkEmail('test@example.com');
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
'https://wellnuo.smartlaunchhub.com/api/auth/check-email',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ email: 'test@example.com' }),
|
|
})
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.data?.exists).toBe(true);
|
|
});
|
|
|
|
it('should handle network errors', async () => {
|
|
global.fetch = jest.fn(() =>
|
|
Promise.reject(new Error('Network error'))
|
|
) as jest.Mock;
|
|
|
|
const result = await api.checkEmail('test@example.com');
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.error?.message).toContain('Network error');
|
|
});
|
|
});
|
|
|
|
describe('Data Sync - Same Backend as Mobile App', () => {
|
|
const WELLNUO_API_URL = 'https://wellnuo.smartlaunchhub.com/api';
|
|
|
|
it('should fetch beneficiaries from WellNuo API', async () => {
|
|
const testToken = 'valid-jwt-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
const mockBeneficiaries = {
|
|
beneficiaries: [
|
|
{
|
|
id: 1,
|
|
name: 'John Doe',
|
|
displayName: 'John Doe',
|
|
email: 'john@example.com',
|
|
equipmentStatus: 'active',
|
|
hasDevices: true,
|
|
},
|
|
],
|
|
};
|
|
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockBeneficiaries),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.getAllBeneficiaries();
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
`${WELLNUO_API_URL}/me/beneficiaries`,
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${testToken}`,
|
|
},
|
|
})
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.data).toHaveLength(1);
|
|
expect(result.data?.[0].id).toBe(1);
|
|
expect(result.data?.[0].displayName).toBe('John Doe');
|
|
});
|
|
|
|
it('should fetch user profile from WellNuo API', async () => {
|
|
const testToken = 'valid-jwt-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
const mockProfile = {
|
|
id: 42,
|
|
email: 'user@example.com',
|
|
firstName: 'Test',
|
|
lastName: 'User',
|
|
phone: '+1234567890',
|
|
user: {
|
|
id: 42,
|
|
email: 'user@example.com',
|
|
firstName: 'Test',
|
|
lastName: 'User',
|
|
},
|
|
};
|
|
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockProfile),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.getProfile();
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
`${WELLNUO_API_URL}/auth/me`,
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${testToken}`,
|
|
},
|
|
})
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.data?.email).toBe('user@example.com');
|
|
expect(result.data?.firstName).toBe('Test');
|
|
});
|
|
|
|
it('should update profile via WellNuo API', async () => {
|
|
const testToken = 'valid-jwt-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
const mockResponse = {
|
|
id: 42,
|
|
email: 'user@example.com',
|
|
firstName: 'Updated',
|
|
lastName: 'Name',
|
|
phone: '+1234567890',
|
|
};
|
|
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockResponse),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.updateProfile({
|
|
firstName: 'Updated',
|
|
lastName: 'Name',
|
|
});
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
`${WELLNUO_API_URL}/auth/profile`,
|
|
expect.objectContaining({
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Authorization': `Bearer ${testToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ firstName: 'Updated', lastName: 'Name' }),
|
|
})
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.data?.firstName).toBe('Updated');
|
|
});
|
|
|
|
it('should create beneficiary via WellNuo API', async () => {
|
|
const testToken = 'valid-jwt-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
const mockResponse = {
|
|
beneficiary: {
|
|
id: 123,
|
|
name: 'New Beneficiary',
|
|
},
|
|
};
|
|
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockResponse),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.createBeneficiary({
|
|
name: 'New Beneficiary',
|
|
phone: '+1234567890',
|
|
});
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
`${WELLNUO_API_URL}/me/beneficiaries`,
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${testToken}`,
|
|
},
|
|
})
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.data?.id).toBe(123);
|
|
expect(result.data?.name).toBe('New Beneficiary');
|
|
});
|
|
|
|
it('should delete beneficiary via WellNuo API', async () => {
|
|
const testToken = 'valid-jwt-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: true,
|
|
json: () => Promise.resolve({ success: true }),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.deleteBeneficiary(123);
|
|
|
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
`${WELLNUO_API_URL}/me/beneficiaries/123`,
|
|
expect.objectContaining({
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${testToken}`,
|
|
},
|
|
})
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.data?.success).toBe(true);
|
|
});
|
|
|
|
it('should handle 401 unauthorized by triggering callback', async () => {
|
|
const testToken = 'expired-token';
|
|
localStorageMock.setItem('accessToken', testToken);
|
|
|
|
global.fetch = jest.fn(() =>
|
|
Promise.resolve({
|
|
ok: false,
|
|
status: 401,
|
|
json: () => Promise.resolve({ error: 'Unauthorized' }),
|
|
})
|
|
) as jest.Mock;
|
|
|
|
const result = await api.getAllBeneficiaries();
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(result.error?.code).toBe('UNAUTHORIZED');
|
|
});
|
|
});
|
|
});
|