mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
test(client): expand frontend test suite to 69.1% coverage
Add and extend tests across 32 files (+10 595 lines) covering Admin panels (AuditLog, Backup, DevNotifications, GitHub), Collab (Chat, Notes, Panel, Polls), Planner (DayDetailPanel, DayPlanSidebar), Settings (DisplaySettings, Integrations, MapSettings), Files (FileManager, FilesPage), Map, Layout (DemoBanner, InAppNotificationBell), shared pickers (CustomDateTimePicker, CustomTimePicker), Vacay holidays, pages (Dashboard, Login), unit stores (authStore, inAppNotificationStore), API (authUrl, client integration), and i18n. Also updates sonar-project.properties and MSW trip handlers to support the new cases.
This commit is contained in:
@@ -20,7 +20,31 @@ vi.mock('../../../src/api/websocket', () => ({
|
||||
const wsMock = await import('../../../src/api/websocket');
|
||||
|
||||
// Import the API client AFTER the mock is set up so it picks up our getSocketId mock
|
||||
const { authApi } = await import('../../../src/api/client');
|
||||
const {
|
||||
authApi,
|
||||
tripsApi,
|
||||
placesApi,
|
||||
packingApi,
|
||||
inAppNotificationsApi,
|
||||
shareApi,
|
||||
backupApi,
|
||||
daysApi,
|
||||
assignmentsApi,
|
||||
tagsApi,
|
||||
categoriesApi,
|
||||
adminApi,
|
||||
addonsApi,
|
||||
mapsApi,
|
||||
budgetApi,
|
||||
filesApi,
|
||||
reservationsApi,
|
||||
weatherApi,
|
||||
settingsApi,
|
||||
accommodationsApi,
|
||||
dayNotesApi,
|
||||
collabApi,
|
||||
notificationsApi,
|
||||
} = await import('../../../src/api/client');
|
||||
|
||||
describe('API client interceptors', () => {
|
||||
beforeEach(() => {
|
||||
@@ -221,4 +245,660 @@ describe('API client interceptors', () => {
|
||||
|
||||
await expect(authApi.me()).rejects.toThrow();
|
||||
});
|
||||
|
||||
// ── 401 edge cases ───────────────────────────────────────────────────────────
|
||||
|
||||
it('FE-API-008: 401 AUTH_REQUIRED on /register path does not redirect', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: { href: 'http://localhost/register', pathname: '/register', search: '' },
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.get('/api/auth/me', () => {
|
||||
return HttpResponse.json({ code: 'AUTH_REQUIRED' }, { status: 401 });
|
||||
})
|
||||
);
|
||||
|
||||
try { await authApi.me(); } catch { /* expected */ }
|
||||
|
||||
expect(window.location.href).toBe('http://localhost/register');
|
||||
});
|
||||
|
||||
it('FE-API-009: 401 AUTH_REQUIRED on /shared/:token path does not redirect', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: { href: 'http://localhost/shared/abc123', pathname: '/shared/abc123', search: '' },
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.get('/api/auth/me', () => {
|
||||
return HttpResponse.json({ code: 'AUTH_REQUIRED' }, { status: 401 });
|
||||
})
|
||||
);
|
||||
|
||||
try { await authApi.me(); } catch { /* expected */ }
|
||||
|
||||
expect(window.location.href).toBe('http://localhost/shared/abc123');
|
||||
});
|
||||
|
||||
it('FE-API-010: 401 AUTH_REQUIRED still rejects the promise even when redirect fires', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: { href: 'http://localhost/dashboard', pathname: '/dashboard', search: '' },
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.get('/api/auth/me', () => {
|
||||
return HttpResponse.json({ code: 'AUTH_REQUIRED' }, { status: 401 });
|
||||
})
|
||||
);
|
||||
|
||||
await expect(authApi.me()).rejects.toThrow();
|
||||
});
|
||||
|
||||
// ── 403 edge cases ───────────────────────────────────────────────────────────
|
||||
|
||||
it('FE-API-011: 403 without MFA_REQUIRED code does not redirect', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: { href: 'http://localhost/dashboard', pathname: '/dashboard', search: '' },
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.get('/api/auth/me', () => {
|
||||
return HttpResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
})
|
||||
);
|
||||
|
||||
try { await authApi.me(); } catch { /* expected */ }
|
||||
|
||||
expect(window.location.href).toBe('http://localhost/dashboard');
|
||||
});
|
||||
|
||||
it('FE-API-012: 403 MFA_REQUIRED still rejects the promise after redirect fires', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: { href: 'http://localhost/dashboard', pathname: '/dashboard', search: '' },
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.get('/api/auth/me', () => {
|
||||
return HttpResponse.json({ code: 'MFA_REQUIRED' }, { status: 403 });
|
||||
})
|
||||
);
|
||||
|
||||
await expect(authApi.me()).rejects.toThrow();
|
||||
});
|
||||
|
||||
// ── backupApi.download ───────────────────────────────────────────────────────
|
||||
|
||||
it('FE-API-013: backupApi.download creates a temp anchor and clicks it', async () => {
|
||||
const createObjectURL = vi.fn(() => 'blob:mock-url');
|
||||
const revokeObjectURL = vi.fn();
|
||||
Object.defineProperty(URL, 'createObjectURL', { writable: true, value: createObjectURL });
|
||||
Object.defineProperty(URL, 'revokeObjectURL', { writable: true, value: revokeObjectURL });
|
||||
|
||||
// Spy on createElement to intercept the anchor click
|
||||
const originalCreate = document.createElement.bind(document);
|
||||
const clickSpy = vi.fn();
|
||||
vi.spyOn(document, 'createElement').mockImplementation((tag: string) => {
|
||||
const el = originalCreate(tag);
|
||||
if (tag === 'a') {
|
||||
Object.defineProperty(el, 'click', { writable: true, value: clickSpy });
|
||||
}
|
||||
return el;
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.get('/api/backup/download/backup.zip', () => {
|
||||
return new HttpResponse(new Blob(['zip-bytes'], { type: 'application/zip' }), { status: 200 });
|
||||
})
|
||||
);
|
||||
|
||||
await expect(backupApi.download('backup.zip')).resolves.toBeUndefined();
|
||||
expect(createObjectURL).toHaveBeenCalled();
|
||||
expect(revokeObjectURL).toHaveBeenCalled();
|
||||
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('FE-API-014: backupApi.download throws when response is not ok', async () => {
|
||||
server.use(
|
||||
http.get('/api/backup/download/missing.zip', () => {
|
||||
return new HttpResponse(null, { status: 404 });
|
||||
})
|
||||
);
|
||||
|
||||
await expect(backupApi.download('missing.zip')).rejects.toThrow('Download failed');
|
||||
});
|
||||
|
||||
// ── API namespace URL spot-checks ────────────────────────────────────────────
|
||||
|
||||
it('FE-API-015: tripsApi.list() makes GET to /api/trips', async () => {
|
||||
server.use(
|
||||
http.get('/api/trips', () => HttpResponse.json([]))
|
||||
);
|
||||
|
||||
const result = await tripsApi.list();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-API-016: tripsApi.get(42) makes GET to /api/trips/42', async () => {
|
||||
let hitUrl = '';
|
||||
server.use(
|
||||
http.get('/api/trips/42', ({ request }) => {
|
||||
hitUrl = new URL(request.url).pathname;
|
||||
return HttpResponse.json({ id: 42 });
|
||||
})
|
||||
);
|
||||
|
||||
await tripsApi.get(42);
|
||||
expect(hitUrl).toBe('/api/trips/42');
|
||||
});
|
||||
|
||||
it('FE-API-017: placesApi.create posts to /api/trips/1/places and returns data directly', async () => {
|
||||
const place = { id: 1, name: 'Paris', trip_id: 1 };
|
||||
server.use(
|
||||
http.post('/api/trips/1/places', () => HttpResponse.json(place))
|
||||
);
|
||||
|
||||
const result = await placesApi.create(1, { name: 'Paris' });
|
||||
expect(result).toMatchObject({ name: 'Paris' });
|
||||
});
|
||||
|
||||
it('FE-API-018: packingApi.bulkImport posts correct payload', async () => {
|
||||
let receivedBody: unknown;
|
||||
server.use(
|
||||
http.post('/api/trips/1/packing/import', async ({ request }) => {
|
||||
receivedBody = await request.json();
|
||||
return HttpResponse.json({ imported: 1 });
|
||||
})
|
||||
);
|
||||
|
||||
await packingApi.bulkImport(1, [{ name: 'Sunscreen' }]);
|
||||
expect(receivedBody).toMatchObject({ items: [{ name: 'Sunscreen' }] });
|
||||
});
|
||||
|
||||
it('FE-API-019: inAppNotificationsApi.list passes unread_only query param', async () => {
|
||||
let searchParams: URLSearchParams | null = null;
|
||||
server.use(
|
||||
http.get('/api/notifications/in-app', ({ request }) => {
|
||||
searchParams = new URL(request.url).searchParams;
|
||||
return HttpResponse.json([]);
|
||||
})
|
||||
);
|
||||
|
||||
await inAppNotificationsApi.list({ unread_only: true });
|
||||
expect(searchParams?.get('unread_only')).toBe('true');
|
||||
});
|
||||
|
||||
it('FE-API-020: shareApi.getSharedTrip hits /api/shared/tok123', async () => {
|
||||
let hitPath = '';
|
||||
server.use(
|
||||
http.get('/api/shared/tok123', ({ request }) => {
|
||||
hitPath = new URL(request.url).pathname;
|
||||
return HttpResponse.json({ token: 'tok123' });
|
||||
})
|
||||
);
|
||||
|
||||
const result = await shareApi.getSharedTrip('tok123');
|
||||
expect(hitPath).toBe('/api/shared/tok123');
|
||||
expect(result).toMatchObject({ token: 'tok123' });
|
||||
});
|
||||
|
||||
// ── authApi method spot-checks ───────────────────────────────────────────────
|
||||
|
||||
it('FE-API-021: authApi.login posts email and password to /api/auth/login', async () => {
|
||||
const user = buildUser();
|
||||
let receivedBody: unknown;
|
||||
server.use(
|
||||
http.post('/api/auth/login', async ({ request }) => {
|
||||
receivedBody = await request.json();
|
||||
return HttpResponse.json({ user });
|
||||
})
|
||||
);
|
||||
|
||||
const result = await authApi.login({ email: 'a@b.com', password: 'pass' });
|
||||
expect(receivedBody).toMatchObject({ email: 'a@b.com', password: 'pass' });
|
||||
expect(result).toMatchObject({ user: { id: user.id } });
|
||||
});
|
||||
|
||||
it('FE-API-022: authApi.uploadAvatar sends multipart/form-data', async () => {
|
||||
let contentType = '';
|
||||
server.use(
|
||||
http.post('/api/auth/avatar', ({ request }) => {
|
||||
contentType = request.headers.get('Content-Type') ?? '';
|
||||
return HttpResponse.json({ avatar_url: '/uploads/avatar.jpg' });
|
||||
})
|
||||
);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', new Blob(['img'], { type: 'image/jpeg' }), 'avatar.jpg');
|
||||
|
||||
await authApi.uploadAvatar(formData);
|
||||
expect(contentType).toMatch(/multipart\/form-data/);
|
||||
});
|
||||
|
||||
it('FE-API-023: authApi.mcpTokens.create posts name to /api/auth/mcp-tokens', async () => {
|
||||
let receivedBody: unknown;
|
||||
server.use(
|
||||
http.post('/api/auth/mcp-tokens', async ({ request }) => {
|
||||
receivedBody = await request.json();
|
||||
return HttpResponse.json({ id: 1, name: 'My Token', token: 'tok' });
|
||||
})
|
||||
);
|
||||
|
||||
await authApi.mcpTokens.create('My Token');
|
||||
expect(receivedBody).toMatchObject({ name: 'My Token' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('API namespace smoke tests', () => {
|
||||
it('daysApi.list fetches trip days', async () => {
|
||||
server.use(http.get('/api/trips/1/days', () => HttpResponse.json([])));
|
||||
await expect(daysApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('assignmentsApi.list fetches day assignments', async () => {
|
||||
server.use(http.get('/api/trips/1/days/1/assignments', () => HttpResponse.json([])));
|
||||
await expect(assignmentsApi.list(1, 1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('tagsApi.list fetches tags', async () => {
|
||||
server.use(http.get('/api/tags', () => HttpResponse.json([])));
|
||||
await expect(tagsApi.list()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('categoriesApi.list fetches categories', async () => {
|
||||
server.use(http.get('/api/categories', () => HttpResponse.json([])));
|
||||
await expect(categoriesApi.list()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('adminApi.users fetches admin users', async () => {
|
||||
server.use(http.get('/api/admin/users', () => HttpResponse.json([])));
|
||||
await expect(adminApi.users()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('addonsApi.enabled fetches enabled addons', async () => {
|
||||
server.use(http.get('/api/addons', () => HttpResponse.json([])));
|
||||
await expect(addonsApi.enabled()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('mapsApi.search posts query', async () => {
|
||||
server.use(http.post('/api/maps/search', () => HttpResponse.json({ results: [] })));
|
||||
await expect(mapsApi.search('Paris')).resolves.toMatchObject({ results: [] });
|
||||
});
|
||||
|
||||
it('budgetApi.list fetches budget items', async () => {
|
||||
server.use(http.get('/api/trips/1/budget', () => HttpResponse.json([])));
|
||||
await expect(budgetApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('filesApi.list fetches trip files', async () => {
|
||||
server.use(http.get('/api/trips/1/files', () => HttpResponse.json([])));
|
||||
await expect(filesApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('reservationsApi.list fetches reservations', async () => {
|
||||
server.use(http.get('/api/trips/1/reservations', () => HttpResponse.json([])));
|
||||
await expect(reservationsApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('weatherApi.get fetches weather data', async () => {
|
||||
server.use(http.get('/api/weather', () => HttpResponse.json({ temp: 20 })));
|
||||
await expect(weatherApi.get(48.8, 2.3, '2025-06-01')).resolves.toMatchObject({ temp: 20 });
|
||||
});
|
||||
|
||||
it('settingsApi.get fetches settings', async () => {
|
||||
server.use(http.get('/api/settings', () => HttpResponse.json({ dark_mode: false })));
|
||||
await expect(settingsApi.get()).resolves.toMatchObject({ dark_mode: false });
|
||||
});
|
||||
|
||||
it('accommodationsApi.list fetches accommodations', async () => {
|
||||
server.use(http.get('/api/trips/1/accommodations', () => HttpResponse.json([])));
|
||||
await expect(accommodationsApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('dayNotesApi.list fetches day notes', async () => {
|
||||
server.use(http.get('/api/trips/1/days/1/notes', () => HttpResponse.json([])));
|
||||
await expect(dayNotesApi.list(1, 1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('collabApi.getNotes fetches collab notes', async () => {
|
||||
server.use(http.get('/api/trips/1/collab/notes', () => HttpResponse.json([])));
|
||||
await expect(collabApi.getNotes(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('notificationsApi.getPreferences fetches preferences', async () => {
|
||||
server.use(http.get('/api/notifications/preferences', () => HttpResponse.json({ email: true })));
|
||||
await expect(notificationsApi.getPreferences()).resolves.toMatchObject({ email: true });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.unreadCount fetches unread count', async () => {
|
||||
server.use(http.get('/api/notifications/in-app/unread-count', () => HttpResponse.json({ count: 3 })));
|
||||
await expect(inAppNotificationsApi.unreadCount()).resolves.toMatchObject({ count: 3 });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.markRead marks a notification read', async () => {
|
||||
server.use(http.put('/api/notifications/in-app/5/read', () => HttpResponse.json({ ok: true })));
|
||||
await expect(inAppNotificationsApi.markRead(5)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.markAllRead marks all notifications read', async () => {
|
||||
server.use(http.put('/api/notifications/in-app/read-all', () => HttpResponse.json({ ok: true })));
|
||||
await expect(inAppNotificationsApi.markAllRead()).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.delete deletes a notification', async () => {
|
||||
server.use(http.delete('/api/notifications/in-app/5', () => HttpResponse.json({ ok: true })));
|
||||
await expect(inAppNotificationsApi.delete(5)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.markUnread marks a notification unread', async () => {
|
||||
server.use(http.put('/api/notifications/in-app/5/unread', () => HttpResponse.json({ ok: true })));
|
||||
await expect(inAppNotificationsApi.markUnread(5)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.deleteAll deletes all notifications', async () => {
|
||||
server.use(http.delete('/api/notifications/in-app/all', () => HttpResponse.json({ ok: true })));
|
||||
await expect(inAppNotificationsApi.deleteAll()).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('inAppNotificationsApi.respond posts a response', async () => {
|
||||
server.use(http.post('/api/notifications/in-app/5/respond', () => HttpResponse.json({ ok: true })));
|
||||
await expect(inAppNotificationsApi.respond(5, 'positive')).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('notificationsApi.updatePreferences updates preferences', async () => {
|
||||
server.use(http.put('/api/notifications/preferences', () => HttpResponse.json({ ok: true })));
|
||||
await expect(notificationsApi.updatePreferences({ email: { trip_invite: true } })).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('backupApi.list fetches backup list', async () => {
|
||||
server.use(http.get('/api/backup/list', () => HttpResponse.json([])));
|
||||
await expect(backupApi.list()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
// ── tripsApi additional methods ──────────────────────────────────────────────
|
||||
|
||||
it('tripsApi.create posts new trip', async () => {
|
||||
server.use(http.post('/api/trips', () => HttpResponse.json({ id: 1, name: 'Test' })));
|
||||
await expect(tripsApi.create({ name: 'Test' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('tripsApi.update puts trip data', async () => {
|
||||
server.use(http.put('/api/trips/1', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(tripsApi.update(1, { name: 'Updated' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('tripsApi.delete deletes a trip', async () => {
|
||||
server.use(http.delete('/api/trips/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(tripsApi.delete(1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('tripsApi.getMembers fetches trip members', async () => {
|
||||
server.use(http.get('/api/trips/1/members', () => HttpResponse.json([])));
|
||||
await expect(tripsApi.getMembers(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('tripsApi.copy copies a trip', async () => {
|
||||
server.use(http.post('/api/trips/1/copy', () => HttpResponse.json({ id: 99 })));
|
||||
await expect(tripsApi.copy(1)).resolves.toMatchObject({ id: 99 });
|
||||
});
|
||||
|
||||
// ── placesApi additional methods ─────────────────────────────────────────────
|
||||
|
||||
it('placesApi.list fetches places', async () => {
|
||||
server.use(http.get('/api/trips/1/places', () => HttpResponse.json([])));
|
||||
await expect(placesApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('placesApi.get fetches a place', async () => {
|
||||
server.use(http.get('/api/trips/1/places/5', () => HttpResponse.json({ id: 5 })));
|
||||
await expect(placesApi.get(1, 5)).resolves.toMatchObject({ id: 5 });
|
||||
});
|
||||
|
||||
it('placesApi.update updates a place', async () => {
|
||||
server.use(http.put('/api/trips/1/places/5', () => HttpResponse.json({ id: 5 })));
|
||||
await expect(placesApi.update(1, 5, { name: 'Rome' })).resolves.toMatchObject({ id: 5 });
|
||||
});
|
||||
|
||||
it('placesApi.delete deletes a place', async () => {
|
||||
server.use(http.delete('/api/trips/1/places/5', () => HttpResponse.json({ ok: true })));
|
||||
await expect(placesApi.delete(1, 5)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── packingApi additional methods ────────────────────────────────────────────
|
||||
|
||||
it('packingApi.list fetches packing items', async () => {
|
||||
server.use(http.get('/api/trips/1/packing', () => HttpResponse.json([])));
|
||||
await expect(packingApi.list(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('packingApi.create creates a packing item', async () => {
|
||||
server.use(http.post('/api/trips/1/packing', () => HttpResponse.json({ id: 1, name: 'Towel' })));
|
||||
await expect(packingApi.create(1, { name: 'Towel' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('packingApi.delete deletes a packing item', async () => {
|
||||
server.use(http.delete('/api/trips/1/packing/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(packingApi.delete(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── assignmentsApi additional methods ────────────────────────────────────────
|
||||
|
||||
it('assignmentsApi.create creates an assignment', async () => {
|
||||
server.use(http.post('/api/trips/1/days/1/assignments', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(assignmentsApi.create(1, 1, { place_id: 5 })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('assignmentsApi.delete deletes an assignment', async () => {
|
||||
server.use(http.delete('/api/trips/1/days/1/assignments/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(assignmentsApi.delete(1, 1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('assignmentsApi.reorder reorders assignments', async () => {
|
||||
server.use(http.put('/api/trips/1/days/1/assignments/reorder', () => HttpResponse.json({ ok: true })));
|
||||
await expect(assignmentsApi.reorder(1, 1, [3, 1, 2])).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── daysApi additional methods ───────────────────────────────────────────────
|
||||
|
||||
it('daysApi.create creates a day', async () => {
|
||||
server.use(http.post('/api/trips/1/days', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(daysApi.create(1, { date: '2025-06-01' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('daysApi.delete deletes a day', async () => {
|
||||
server.use(http.delete('/api/trips/1/days/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(daysApi.delete(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── tagsApi / categoriesApi additional methods ────────────────────────────────
|
||||
|
||||
it('tagsApi.create creates a tag', async () => {
|
||||
server.use(http.post('/api/tags', () => HttpResponse.json({ id: 1, name: 'Fun' })));
|
||||
await expect(tagsApi.create({ name: 'Fun' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('tagsApi.delete deletes a tag', async () => {
|
||||
server.use(http.delete('/api/tags/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(tagsApi.delete(1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('categoriesApi.create creates a category', async () => {
|
||||
server.use(http.post('/api/categories', () => HttpResponse.json({ id: 1, name: 'Food' })));
|
||||
await expect(categoriesApi.create({ name: 'Food' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('categoriesApi.delete deletes a category', async () => {
|
||||
server.use(http.delete('/api/categories/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(categoriesApi.delete(1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── adminApi additional methods ───────────────────────────────────────────────
|
||||
|
||||
it('adminApi.stats fetches admin stats', async () => {
|
||||
server.use(http.get('/api/admin/stats', () => HttpResponse.json({ trips: 5 })));
|
||||
await expect(adminApi.stats()).resolves.toMatchObject({ trips: 5 });
|
||||
});
|
||||
|
||||
it('adminApi.createUser creates a user', async () => {
|
||||
server.use(http.post('/api/admin/users', () => HttpResponse.json({ id: 10 })));
|
||||
await expect(adminApi.createUser({ email: 'x@x.com' })).resolves.toMatchObject({ id: 10 });
|
||||
});
|
||||
|
||||
// ── budgetApi additional methods ─────────────────────────────────────────────
|
||||
|
||||
it('budgetApi.create creates a budget item', async () => {
|
||||
server.use(http.post('/api/trips/1/budget', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(budgetApi.create(1, { name: 'Hotel' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('budgetApi.delete deletes a budget item', async () => {
|
||||
server.use(http.delete('/api/trips/1/budget/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(budgetApi.delete(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── reservationsApi additional methods ───────────────────────────────────────
|
||||
|
||||
it('reservationsApi.create creates a reservation', async () => {
|
||||
server.use(http.post('/api/trips/1/reservations', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(reservationsApi.create(1, { name: 'Hotel' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('reservationsApi.delete deletes a reservation', async () => {
|
||||
server.use(http.delete('/api/trips/1/reservations/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(reservationsApi.delete(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── settingsApi additional methods ───────────────────────────────────────────
|
||||
|
||||
it('settingsApi.set updates a setting', async () => {
|
||||
server.use(http.put('/api/settings', () => HttpResponse.json({ ok: true })));
|
||||
await expect(settingsApi.set('dark_mode', true)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── accommodationsApi additional methods ─────────────────────────────────────
|
||||
|
||||
it('accommodationsApi.create creates accommodation', async () => {
|
||||
server.use(http.post('/api/trips/1/accommodations', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(accommodationsApi.create(1, { name: 'Hotel' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('accommodationsApi.delete deletes accommodation', async () => {
|
||||
server.use(http.delete('/api/trips/1/accommodations/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(accommodationsApi.delete(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── dayNotesApi additional methods ───────────────────────────────────────────
|
||||
|
||||
it('dayNotesApi.create creates a day note', async () => {
|
||||
server.use(http.post('/api/trips/1/days/1/notes', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(dayNotesApi.create(1, 1, { text: 'Hello' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('dayNotesApi.delete deletes a day note', async () => {
|
||||
server.use(http.delete('/api/trips/1/days/1/notes/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(dayNotesApi.delete(1, 1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── collabApi additional methods ─────────────────────────────────────────────
|
||||
|
||||
it('collabApi.createNote creates a note', async () => {
|
||||
server.use(http.post('/api/trips/1/collab/notes', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(collabApi.createNote(1, { title: 'Note' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('collabApi.deleteNote deletes a note', async () => {
|
||||
server.use(http.delete('/api/trips/1/collab/notes/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(collabApi.deleteNote(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── backupApi additional methods ─────────────────────────────────────────────
|
||||
|
||||
it('backupApi.getAutoSettings fetches auto backup settings', async () => {
|
||||
server.use(http.get('/api/backup/auto-settings', () => HttpResponse.json({ enabled: true })));
|
||||
await expect(backupApi.getAutoSettings()).resolves.toMatchObject({ enabled: true });
|
||||
});
|
||||
|
||||
it('backupApi.delete deletes a backup', async () => {
|
||||
server.use(http.delete('/api/backup/backup.zip', () => HttpResponse.json({ ok: true })));
|
||||
await expect(backupApi.delete('backup.zip')).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── shareApi additional methods ───────────────────────────────────────────────
|
||||
|
||||
it('shareApi.createLink creates a share link', async () => {
|
||||
server.use(http.post('/api/trips/1/share-link', () => HttpResponse.json({ token: 'abc' })));
|
||||
await expect(shareApi.createLink(1)).resolves.toMatchObject({ token: 'abc' });
|
||||
});
|
||||
|
||||
it('shareApi.deleteLink deletes a share link', async () => {
|
||||
server.use(http.delete('/api/trips/1/share-link', () => HttpResponse.json({ ok: true })));
|
||||
await expect(shareApi.deleteLink(1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── notificationsApi additional methods ───────────────────────────────────────
|
||||
|
||||
it('notificationsApi.testWebhook tests webhook endpoint', async () => {
|
||||
server.use(http.post('/api/notifications/test-webhook', () => HttpResponse.json({ ok: true })));
|
||||
await expect(notificationsApi.testWebhook('http://example.com')).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('notificationsApi.testSmtp tests smtp endpoint', async () => {
|
||||
server.use(http.post('/api/notifications/test-smtp', () => HttpResponse.json({ ok: true })));
|
||||
await expect(notificationsApi.testSmtp('user@example.com')).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
// ── mapsApi additional methods ────────────────────────────────────────────────
|
||||
|
||||
it('mapsApi.reverse fetches reverse geocode', async () => {
|
||||
server.use(http.get('/api/maps/reverse', () => HttpResponse.json({ address: 'Paris' })));
|
||||
await expect(mapsApi.reverse(48.8, 2.3)).resolves.toMatchObject({ address: 'Paris' });
|
||||
});
|
||||
|
||||
// ── collabApi messaging methods ───────────────────────────────────────────────
|
||||
|
||||
it('collabApi.getMessages fetches messages', async () => {
|
||||
server.use(http.get('/api/trips/1/collab/messages', () => HttpResponse.json([])));
|
||||
await expect(collabApi.getMessages(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('collabApi.sendMessage sends a message', async () => {
|
||||
server.use(http.post('/api/trips/1/collab/messages', () => HttpResponse.json({ id: 1 })));
|
||||
await expect(collabApi.sendMessage(1, { text: 'Hello' })).resolves.toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('collabApi.deleteMessage deletes a message', async () => {
|
||||
server.use(http.delete('/api/trips/1/collab/messages/1', () => HttpResponse.json({ ok: true })));
|
||||
await expect(collabApi.deleteMessage(1, 1)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('collabApi.reactMessage reacts to a message', async () => {
|
||||
server.use(http.post('/api/trips/1/collab/messages/1/react', () => HttpResponse.json({ ok: true })));
|
||||
await expect(collabApi.reactMessage(1, 1, '👍')).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('collabApi.getPolls fetches polls', async () => {
|
||||
server.use(http.get('/api/trips/1/collab/polls', () => HttpResponse.json([])));
|
||||
await expect(collabApi.getPolls(1)).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('backupApi.uploadRestore uploads and restores a backup', async () => {
|
||||
server.use(http.post('/api/backup/upload-restore', () => HttpResponse.json({ ok: true })));
|
||||
const file = new File(['data'], 'backup.zip', { type: 'application/zip' });
|
||||
await expect(backupApi.uploadRestore(file)).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('backupApi.restore restores a named backup', async () => {
|
||||
server.use(http.post('/api/backup/restore/backup.zip', () => HttpResponse.json({ ok: true })));
|
||||
await expect(backupApi.restore('backup.zip')).resolves.toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it('backupApi.create creates a backup', async () => {
|
||||
server.use(http.post('/api/backup/create', () => HttpResponse.json({ filename: 'backup.zip' })));
|
||||
await expect(backupApi.create()).resolves.toMatchObject({ filename: 'backup.zip' });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user