83 lines
3.3 KiB
JavaScript
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' });
|
|
});
|
|
});
|