Files
2026-09-24 17:57:54 +02:00

83 lines
3.3 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest';
import { ApiClient } from '../../app/static/js/systems/ApiClient.js';
const endpoints = { highscores: '/api/highscores', runs: '/api/runs' };
/** Baut eine fetch-Attrappe mit fester Antwort. */
function fakeFetch(status, body) {
return vi.fn(async () => ({
status,
json: async () => {
if (body === undefined) throw new SyntaxError('kein JSON');
return body;
},
}));
}
describe('ApiClient', () => {
it('holt ein Run-Token', async () => {
const fetch = fakeFetch(201, { run_token: 'abc' });
expect(await new ApiClient(endpoints, fetch).startRun()).toBe('abc');
expect(fetch).toHaveBeenCalledWith('/api/runs', expect.objectContaining({ method: 'POST' }));
});
it('liefert null statt Fehler, wenn der Server nicht erreichbar ist', async () => {
const fetch = vi.fn(async () => {
throw new TypeError('Failed to fetch');
});
const client = new ApiClient(endpoints, fetch);
expect(await client.startRun()).toBeNull();
expect(await client.fetchHighscores()).toEqual({ ok: false, highscores: [] });
expect(await client.submitHighscore({ name: 'A', score: 1, wave: 1, durationS: 1, runToken: 't' })).toMatchObject({
ok: false,
error: 'network',
});
});
it('liest die Highscore-Liste', async () => {
const entries = [{ rank: 1, name: 'Ada', score: 10, wave: 1, created_at: 'x' }];
const result = await new ApiClient(endpoints, fakeFetch(200, { highscores: entries })).fetchHighscores();
expect(result).toEqual({ ok: true, highscores: entries });
});
it('behandelt kaputte Antworten als Fehlschlag', async () => {
expect(await new ApiClient(endpoints, fakeFetch(200, undefined)).fetchHighscores()).toEqual({ ok: false, highscores: [] });
expect(await new ApiClient(endpoints, fakeFetch(500, { error: 'x' })).startRun()).toBeNull();
});
it('sendet Highscores im API-Format mit ganzzahligen Werten', async () => {
const fetch = fakeFetch(201, { rank: 3, highscores: [] });
const result = await new ApiClient(endpoints, fetch).submitHighscore({
name: 'Ada',
score: 1234.7,
wave: 3,
durationS: 95.9,
runToken: 'tok',
});
expect(result).toEqual({ ok: true, rank: 3, highscores: [] });
const [url, init] = fetch.mock.calls[0];
expect(url).toBe('/api/highscores');
expect(init.method).toBe('POST');
expect(init.headers['Content-Type']).toBe('application/json');
expect(JSON.parse(init.body)).toEqual({ name: 'Ada', score: 1234, wave: 3, duration_s: 95, run_token: 'tok' });
});
it('gibt Validierungs- und Rate-Limit-Fehler lesbar zurück', async () => {
const invalid = await new ApiClient(
endpoints,
fakeFetch(400, { error: 'validation_failed', message: 'Ungültige Eingaben.', fields: { name: 'zu lang' } }),
).submitHighscore({ name: 'A', score: 1, wave: 1, durationS: 1, runToken: 't' });
expect(invalid).toMatchObject({ ok: false, error: 'validation_failed', fields: { name: 'zu lang' } });
const limited = await new ApiClient(endpoints, fakeFetch(429, { error: 'rate_limited', message: 'Zu viele' })).submitHighscore({
name: 'A',
score: 1,
wave: 1,
durationS: 1,
runToken: 't',
});
expect(limited).toMatchObject({ ok: false, error: 'rate_limited', message: 'Zu viele' });
});
});