300 lines
10 KiB
TypeScript
300 lines
10 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import * as client from '../../../src/api/client';
|
|
|
|
describe('api client', () => {
|
|
beforeEach(() => {
|
|
vi.stubGlobal('fetch', vi.fn());
|
|
});
|
|
|
|
describe('utility functions', () => {
|
|
it('localHHMMToUTCHHMM converts correctly (assuming UTC+0 for simplicity in test, or handling offset)', () => {
|
|
const result = client.localHHMMToUTCHHMM('12:00');
|
|
expect(result).toMatch(/^\d{2}:\d{2}$/);
|
|
});
|
|
|
|
it('localToUTC converts to ISO string', () => {
|
|
const dateStr = '2026-01-01T12:00';
|
|
const result = client.localToUTC(dateStr);
|
|
expect(result).toBe(new Date(dateStr).toISOString());
|
|
});
|
|
});
|
|
|
|
describe('fetch functions', () => {
|
|
describe('fetchSummary', () => {
|
|
it('returns data on success', async () => {
|
|
const mockData = { total: 10 };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockData,
|
|
} as any);
|
|
|
|
const result = await client.fetchSummary('2026-01-01', '2026-01-02');
|
|
expect(result).toEqual(mockData);
|
|
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/stats/summary'));
|
|
});
|
|
|
|
it('throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.fetchSummary('2026-01-01', '2026-01-02')).rejects.toThrow('Failed to fetch summary');
|
|
});
|
|
});
|
|
|
|
describe('fetchDailySummary', () => {
|
|
it('returns data on success and verifies query params', async () => {
|
|
const mockData = { some: 'data' } as any;
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockData,
|
|
} as any);
|
|
|
|
const from_date = '2026-01-01';
|
|
const to_date = '2026-01-02';
|
|
const day_start = '22:00';
|
|
const day_end = '08:00';
|
|
const timezone_offset = -360;
|
|
|
|
const result = await client.fetchDailySummary(from_date, to_date, day_start, day_end, timezone_offset);
|
|
expect(result).toEqual(mockData);
|
|
|
|
const params = new URLSearchParams({
|
|
from_date,
|
|
to_date,
|
|
day_start,
|
|
day_end,
|
|
timezone_offset: timezone_offset.toString()
|
|
});
|
|
const expectedUrl = `/api/stats/daily-summary?${params.toString()}`;
|
|
expect(fetch).toHaveBeenCalledWith(expect.stringContaining(expectedUrl));
|
|
});
|
|
|
|
it('throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.fetchDailySummary('2026-01-01', '2026-01-02', '22:00', '08:00')).rejects.toThrow('Failed to fetch daily-summary');
|
|
});
|
|
});
|
|
|
|
describe('fetchTimetable', () => {
|
|
it('returns data on success and verifies query params', async () => {
|
|
const mockData = { some: 'timetable' } as any;
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockData,
|
|
} as any);
|
|
|
|
const from_date = '2026-01-01';
|
|
const to_date = '2026-01-02';
|
|
const day_start = '22:00';
|
|
const day_end = '08:00';
|
|
const timezone_offset = 0;
|
|
|
|
const result = await client.fetchTimetable(from_date, to_date, day_start, day_end, timezone_offset);
|
|
expect(result).toEqual(mockData);
|
|
|
|
const params = new URLSearchParams({
|
|
from_date,
|
|
to_date,
|
|
day_start,
|
|
day_end,
|
|
timezone_offset: timezone_offset.toString()
|
|
});
|
|
const expectedUrl = `/api/stats/timetable?${params.toString()}`;
|
|
expect(fetch).toHaveBeenCalledWith(expect.stringContaining(expectedUrl));
|
|
});
|
|
|
|
it('throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.fetchTimetable('2026-01-01', '2026-01-02', '22:00', '08:00')).rejects.toThrow('Failed to fetch timetable');
|
|
});
|
|
});
|
|
|
|
describe('fetchWakeSleepTimes', () => {
|
|
it('returns data on success and transforms time correctly', async () => {
|
|
const mockData = { wakeup_ref: '06:30', bedtime_ref: '21:00' };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockData,
|
|
} as any);
|
|
|
|
const offsetSpy = vi.spyOn(Date.prototype, 'getTimezoneOffset').mockReturnValue(-60);
|
|
|
|
try {
|
|
const result = await client.fetchWakeSleepTimes('2026-01-01', '2026-01-02', '07:30', '22:00', 0);
|
|
expect(result).toEqual(mockData);
|
|
expect(fetch).toHaveBeenCalledWith(
|
|
expect.stringContaining('from_date=2026-01-01&to_date=2026-01-02&wakeup_ref=06%3A30&bedtime_ref=21%3A00&timezone_offset=0')
|
|
);
|
|
} finally {
|
|
offsetSpy.mockRestore();
|
|
}
|
|
});
|
|
|
|
it('throws error on failure', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.fetchWakeSleepTimes('2026-01-01', '2026-01-02', '07:30', '22:00')).rejects.toThrow('Failed to fetch wake-sleep-times');
|
|
});
|
|
});
|
|
|
|
describe('fetchActiveSession', () => {
|
|
it('returns session on success', async () => {
|
|
const mockSession = { id: 1, start_time: '2026-01-01T12:00', end_time: null };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockSession,
|
|
} as any);
|
|
|
|
const result = await client.fetchActiveSession();
|
|
expect(result).toEqual(mockSession);
|
|
});
|
|
|
|
it('returns null on 404/not-ok', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
const result = await client.fetchActiveSession();
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('fetchSessions', () => {
|
|
it('returns session list on success', async () => {
|
|
const mockResponse = { sessions: [{ id: 1, start_time: '2026-01-01T12:00' }] };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
} as any);
|
|
|
|
const result = await client.fetchSessions();
|
|
expect(result).toEqual(mockResponse);
|
|
expect(fetch).toHaveBeenCalledWith(expect.stringContaining('/api/sessions'));
|
|
});
|
|
|
|
it('throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.fetchSessions()).rejects.toThrow('Failed to fetch sessions');
|
|
});
|
|
});
|
|
|
|
describe('Session mutations', () => {
|
|
it('createSession sends POST request with body', async () => {
|
|
const payload = { start_time: '2026-01-01T12:00' };
|
|
const mockResponse = { id: 1, ...payload };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
} as any);
|
|
|
|
const result = await client.createSession(payload as any);
|
|
expect(result).toEqual(mockResponse);
|
|
expect(fetch).toHaveBeenCalledWith(
|
|
expect.stringContaining('/api/sessions'),
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('createSession throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.createSession({ start_time: '2026-01-01T12:00' } as any)).rejects.toThrow('Failed to create session');
|
|
});
|
|
|
|
it('endSession sends PATCH request', async () => {
|
|
const sessionId = 1;
|
|
const payload = { end_time: '2026-01-01T14:00' };
|
|
const mockResponse = { id: 1, ...payload };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
} as any);
|
|
|
|
const result = await client.endSession(sessionId, payload as any);
|
|
expect(result).toEqual(mockResponse);
|
|
expect(fetch).toHaveBeenCalledWith(
|
|
expect.stringContaining(`/api/sessions/${sessionId}/end`),
|
|
expect.objectContaining({
|
|
method: 'PATCH',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('endSession throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.endSession(1, { end_time: '2026-01-01T14:00' } as any)).rejects.toThrow('Failed to end session');
|
|
});
|
|
|
|
it('deleteSession sends DELETE request', async () => {
|
|
const sessionId = 1;
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
} as any);
|
|
|
|
await client.deleteSession(sessionId);
|
|
expect(fetch).toHaveBeenCalledWith(
|
|
expect.stringContaining(`/api/sessions/${sessionId}`),
|
|
expect.objectContaining({ method: 'DELETE' })
|
|
);
|
|
});
|
|
|
|
it('deleteSession throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.deleteSession(1)).rejects.toThrow('Failed to delete session');
|
|
});
|
|
|
|
it('updateSession sends PATCH request', async () => {
|
|
const sessionId = 1;
|
|
const payload = { start_time: '2026-01-01T10:00' };
|
|
const mockResponse = { id: 1, ...payload };
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: true,
|
|
json: async () => mockResponse,
|
|
} as any);
|
|
|
|
const result = await client.updateSession(sessionId, payload as any);
|
|
expect(result).toEqual(mockResponse);
|
|
expect(fetch).toHaveBeenCalledWith(
|
|
expect.stringContaining(`/api/sessions/${sessionId}`),
|
|
expect.objectContaining({
|
|
method: 'PATCH',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('updateSession throws error on non-ok response', async () => {
|
|
vi.mocked(fetch).mockResolvedValue({
|
|
ok: false,
|
|
} as any);
|
|
|
|
await expect(client.updateSession(1, { start_time: '2026-01-01T10:00' } as any)).rejects.toThrow('Failed to update session');
|
|
});
|
|
});
|
|
});
|
|
});
|