feat(community): Datenschicht für Galerie, API und Veröffentlichen
- gallery.ts: Galerie aus Appwrite lesen (Abfragen für Liste, Suche, Cursor, Admin-Filter), getGalleryCard (404 und ungültige ID ergeben null), eigene Veröffentlichungen, Meldungen, Bilder - api.ts: Aufrufe von aircard-api mit JWT (Cache je Nutzer, 10 Minuten, geleert bei Auth-Änderung, genau eine Wiederholung bei 401), ApiError mit code, Netz-/Proxy-Fehler als 503 - publish.ts: rendern, zwei Uploads in gallery-images, API; bei Fehler genau die zwei Dateien dieses Versuchs löschen; normalizeTags - cards.ts: eigenständiges fetchFile(bucketId, fileId); CardsApi.fetchFile bleibt Hülle - appwrite.ts: IDS für gallery, reports, gallery-images Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7bbe0a9fe6
commit
89c86fd578
9 files changed
+1576
-7
No files matched your search
@@ -0,0 +1,418 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
createJWT: vi.fn(),
|
||||
userId: null as string | null,
|
||||
authListeners: [] as Array<(user: unknown) => void>,
|
||||
logoutListeners: [] as Array<() => void>,
|
||||
}));
|
||||
|
||||
vi.mock('../account/appwrite', () => ({ account: { createJWT: mocks.createJWT } }));
|
||||
vi.mock('../account/auth', () => ({
|
||||
currentUserId: () => mocks.userId,
|
||||
onAuthChange: (fn: (user: unknown) => void) => {
|
||||
mocks.authListeners.push(fn);
|
||||
return () => undefined;
|
||||
},
|
||||
onLogout: (fn: () => void) => {
|
||||
mocks.logoutListeners.push(fn);
|
||||
return () => undefined;
|
||||
},
|
||||
}));
|
||||
|
||||
type Api = typeof import('./api');
|
||||
|
||||
const UNAVAILABLE = 'Der Dienst ist gerade nicht erreichbar.';
|
||||
const MINUTE = 60_000;
|
||||
|
||||
// Der JWT-Cache lebt im Modul; jeder Test bekommt eine frische Instanz.
|
||||
async function freshApi(): Promise<Api> {
|
||||
vi.resetModules();
|
||||
return import('./api');
|
||||
}
|
||||
|
||||
const json = (status: number, body: unknown) =>
|
||||
new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
|
||||
|
||||
const fetchMock = vi.fn<(url: string, init: RequestInit) => Promise<Response>>();
|
||||
|
||||
function sentHeaders(call = 0): Record<string, string> {
|
||||
return fetchMock.mock.calls[call][1].headers as Record<string, string>;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers({ toFake: ['Date'] });
|
||||
vi.setSystemTime(new Date('2026-10-04T10:00:00Z'));
|
||||
mocks.createJWT.mockReset();
|
||||
mocks.userId = 'u1';
|
||||
mocks.authListeners.length = 0;
|
||||
mocks.logoutListeners.length = 0;
|
||||
let n = 0;
|
||||
mocks.createJWT.mockImplementation(async () => ({ jwt: `jwt${++n}` }));
|
||||
fetchMock.mockReset();
|
||||
fetchMock.mockImplementation(async () => json(200, { ok: true }));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('apiPost', () => {
|
||||
it('sendet JSON per POST an den Pfad und liefert die JSON-Antwort', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(200, { schonGemeldet: true }));
|
||||
await expect(api.apiPost('/api/melden', { id: 'c1', reason: 'spam' }, { auth: false })).resolves.toEqual({
|
||||
schonGemeldet: true,
|
||||
});
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
const [url, init] = fetchMock.mock.calls[0];
|
||||
expect(url).toBe('/api/melden');
|
||||
expect(init.method).toBe('POST');
|
||||
expect(sentHeaders()['Content-Type']).toBe('application/json');
|
||||
expect(init.body).toBe(JSON.stringify({ id: 'c1', reason: 'spam' }));
|
||||
});
|
||||
|
||||
it('ohne auth: kein JWT, kein createJWT', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/melden', {}, { auth: false });
|
||||
expect(mocks.createJWT).not.toHaveBeenCalled();
|
||||
expect(sentHeaders()['X-Aircard-Jwt']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('mit auth: sendet den Header X-Aircard-Jwt mit dem JWT von createJWT (ohne duration)', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/zurueckziehen', { id: 'c1' }, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledOnce();
|
||||
expect(mocks.createJWT.mock.calls[0]).toEqual([]);
|
||||
expect(sentHeaders()['X-Aircard-Jwt']).toBe('jwt1');
|
||||
});
|
||||
|
||||
describe('JWT-Cache', () => {
|
||||
it('der zweite Aufruf innerhalb von 10 Minuten holt kein neues JWT', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
vi.setSystemTime(Date.now() + 9 * MINUTE + 59_000);
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledOnce();
|
||||
expect(sentHeaders(1)['X-Aircard-Jwt']).toBe('jwt1');
|
||||
});
|
||||
|
||||
it('nach 10 Minuten wird ein neues JWT geholt', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
vi.setSystemTime(Date.now() + 10 * MINUTE);
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
expect(sentHeaders(1)['X-Aircard-Jwt']).toBe('jwt2');
|
||||
});
|
||||
|
||||
it('gilt je Nutzer-ID: ein anderer Nutzer bekommt nie das JWT des vorigen', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
mocks.userId = 'u2';
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
expect(sentHeaders(1)['X-Aircard-Jwt']).toBe('jwt2');
|
||||
});
|
||||
|
||||
it('wird bei einer Änderung des Nutzers (onAuthChange) geleert', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.authListeners).toHaveLength(1);
|
||||
mocks.authListeners[0](null);
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('wird beim Abmelden (onLogout) geleert', async () => {
|
||||
const api = await freshApi();
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.logoutListeners).toHaveLength(1);
|
||||
mocks.logoutListeners[0]();
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('ein JWT, das erst nach einem Nutzerwechsel eintrifft, wird nicht gemerkt', async () => {
|
||||
const api = await freshApi();
|
||||
let release: (value: { jwt: string }) => void = () => undefined;
|
||||
mocks.createJWT.mockImplementationOnce(() => new Promise((resolve) => (release = resolve)));
|
||||
const first = api.apiPost('/api/x', {}, { auth: true });
|
||||
await vi.waitFor(() => expect(mocks.createJWT).toHaveBeenCalledOnce());
|
||||
mocks.authListeners[0](null); // Abmelden, während createJWT noch läuft
|
||||
release({ jwt: 'late' });
|
||||
await first;
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
expect(sentHeaders(1)['X-Aircard-Jwt']).toBe('jwt1');
|
||||
});
|
||||
|
||||
it('ohne bekannte Nutzer-ID wird nichts gemerkt', async () => {
|
||||
const api = await freshApi();
|
||||
mocks.userId = null;
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('401 von der API', () => {
|
||||
it('holt ein neues JWT und wiederholt genau einmal', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValueOnce(json(401, { error: 'Bitte melde dich an.' }));
|
||||
fetchMock.mockResolvedValueOnce(json(200, { ok: true }));
|
||||
await expect(api.apiPost('/api/x', { a: 1 }, { auth: true })).resolves.toEqual({ ok: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(sentHeaders(0)['X-Aircard-Jwt']).toBe('jwt1');
|
||||
expect(sentHeaders(1)['X-Aircard-Jwt']).toBe('jwt2');
|
||||
expect(fetchMock.mock.calls[1][1].body).toBe(JSON.stringify({ a: 1 }));
|
||||
});
|
||||
|
||||
it('das neue JWT wird gemerkt, das alte nicht mehr', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValueOnce(json(401, { error: 'Bitte melde dich an.' }));
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
await api.apiPost('/api/x', {}, { auth: true });
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
expect(sentHeaders(2)['X-Aircard-Jwt']).toBe('jwt2');
|
||||
});
|
||||
|
||||
it('bei einem zweiten 401 ist Schluss: ApiError mit code 401, keine dritte Anfrage', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockImplementation(async () => json(401, { error: 'Bitte melde dich an.' }));
|
||||
const err = await api.apiPost('/api/x', {}, { auth: true }).catch((e: unknown) => e);
|
||||
expect(err).toBeInstanceOf(api.ApiError);
|
||||
expect(err).toMatchObject({ code: 401, message: 'Bitte melde dich an.' });
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.createJWT).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('ohne auth wird nicht wiederholt', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(401, { error: 'Nein.' }));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: false })).rejects.toMatchObject({ code: 401 });
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('andere Fehler (403) lösen keinen Wiederholungsversuch aus', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(403, { error: 'Nur für Admins.' }));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: true })).rejects.toMatchObject({ code: 403 });
|
||||
expect(fetchMock).toHaveBeenCalledOnce();
|
||||
expect(mocks.createJWT).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fehler', () => {
|
||||
it('ApiError trägt Statuscode (code = status) und den deutschen Servertext aus `message`', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(400, { message: 'Bitte gib einen Titel an.' }));
|
||||
const err = await api.apiPost('/api/x', {}, { auth: false }).catch((e: unknown) => e);
|
||||
expect(err).toBeInstanceOf(api.ApiError);
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
expect(err).toMatchObject({ code: 400, status: 400, message: 'Bitte gib einen Titel an.' });
|
||||
});
|
||||
|
||||
it('liest den Text auch aus `error` (so antwortet aircard-api tatsächlich)', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(429, { error: 'Zu viele Meldungen – bitte später nochmal.' }));
|
||||
await expect(api.apiPost('/api/melden', {}, { auth: false })).rejects.toMatchObject({
|
||||
code: 429,
|
||||
message: 'Zu viele Meldungen – bitte später nochmal.',
|
||||
});
|
||||
});
|
||||
|
||||
it('JSON ohne Text: allgemeine Meldung mit dem Statuscode', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(500, {}));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: false })).rejects.toMatchObject({
|
||||
code: 500,
|
||||
message: 'Etwas ist schiefgelaufen. Bitte nochmal versuchen.',
|
||||
});
|
||||
});
|
||||
|
||||
it('der Statuscode lässt isUnauthorized aus errors.ts greifen', async () => {
|
||||
const api = await freshApi();
|
||||
const { isUnauthorized } = await import('../account/errors');
|
||||
fetchMock.mockResolvedValue(json(401, { error: 'x' }));
|
||||
const err = await api.apiPost('/api/x', {}, { auth: false }).catch((e: unknown) => e);
|
||||
expect(isUnauthorized(err)).toBe(true);
|
||||
});
|
||||
|
||||
it('Netzfehler: code 503 und „Der Dienst ist gerade nicht erreichbar.“', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockRejectedValue(new TypeError('Failed to fetch'));
|
||||
const err = await api.apiPost('/api/x', {}, { auth: false }).catch((e: unknown) => e);
|
||||
expect(err).toBeInstanceOf(api.ApiError);
|
||||
expect(err).toMatchObject({ code: 503, message: UNAVAILABLE });
|
||||
});
|
||||
|
||||
it.each([502, 503, 504])('Status %i (auch mit JSON) gilt als nicht erreichbar: code 503', async (status) => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(status, { error: 'irgendwas' }));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: false })).rejects.toMatchObject({ code: 503, message: UNAVAILABLE });
|
||||
});
|
||||
|
||||
it('eine Fehlerseite ohne JSON (z. B. Proxy) gilt als nicht erreichbar', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(new Response('<html>404 page not found</html>', { status: 404 }));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: false })).rejects.toMatchObject({ code: 503, message: UNAVAILABLE });
|
||||
});
|
||||
|
||||
it('eine HTML-Antwort mit Status 200 (Seitenfallback ohne API-Route) gilt als nicht erreichbar', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(new Response('<!doctype html><title>AirCard</title>', { status: 200 }));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: false })).rejects.toMatchObject({ code: 503, message: UNAVAILABLE });
|
||||
});
|
||||
|
||||
it('JSON, das kein Objekt ist, gilt als nicht erreichbar', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(new Response('"ok"', { status: 200 }));
|
||||
await expect(api.apiPost('/api/x', {}, { auth: false })).rejects.toMatchObject({ code: 503 });
|
||||
});
|
||||
|
||||
it('createJWT mit user_auth_method_unsupported: Hinweis an den Betreiber, keine Anfrage', async () => {
|
||||
const api = await freshApi();
|
||||
mocks.createJWT.mockRejectedValue(
|
||||
Object.assign(new Error('JWT unsupported'), { code: 501, type: 'user_auth_method_unsupported' }),
|
||||
);
|
||||
const err = await api.apiPost('/api/x', {}, { auth: true }).catch((e: unknown) => e);
|
||||
expect(err).toBeInstanceOf(api.ApiError);
|
||||
expect(err).toMatchObject({
|
||||
code: 501,
|
||||
message: 'Anmeldung per Token ist auf dem Server ausgeschaltet – bitte den Betreiber informieren.',
|
||||
});
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('createJWT mit 401 (keine Sitzung) geht unverändert durch, damit withLogin greift', async () => {
|
||||
const api = await freshApi();
|
||||
const failure = Object.assign(new Error('missing scope'), { code: 401, type: 'general_unauthorized_scope' });
|
||||
mocks.createJWT.mockRejectedValue(failure);
|
||||
await expect(api.apiPost('/api/x', {}, { auth: true })).rejects.toBe(failure);
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publishCard', () => {
|
||||
const input = {
|
||||
title: 'Meine Karte',
|
||||
issuer: 'Beispielbank',
|
||||
tags: ['visa', 'gold'],
|
||||
displayName: 'Kenso',
|
||||
imageFileId: 'img1',
|
||||
previewFileId: 'pre1',
|
||||
sourceCardId: 'card1',
|
||||
};
|
||||
|
||||
it('POST /api/veroeffentlichen mit JWT, Tags als Liste; liefert { id }', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(201, { id: 'g1' }));
|
||||
await expect(api.publishCard(input)).resolves.toEqual({ id: 'g1' });
|
||||
const [url, init] = fetchMock.mock.calls[0];
|
||||
expect(url).toBe('/api/veroeffentlichen');
|
||||
expect(JSON.parse(init.body as string)).toEqual(input);
|
||||
expect(sentHeaders()['X-Aircard-Jwt']).toBe('jwt1');
|
||||
});
|
||||
|
||||
it('409: „Diese Karte ist schon veröffentlicht.“', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(409, { error: 'Diese Karte ist schon veröffentlicht.' }));
|
||||
const err = await api.publishCard(input).catch((e: unknown) => e);
|
||||
expect(err).toMatchObject({ code: 409, message: 'Diese Karte ist schon veröffentlicht.' });
|
||||
expect(api.isAlreadyPublished(err)).toBe(true);
|
||||
});
|
||||
|
||||
it('403 „bereits veröffentlicht oder gesperrt“ (Dateien belegt) gilt ebenfalls als schon veröffentlicht', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(403, { error: 'Diese Bilder sind bereits veröffentlicht oder gesperrt.' }));
|
||||
const err = await api.publishCard(input).catch((e: unknown) => e);
|
||||
expect(err).toBeInstanceOf(api.ApiError);
|
||||
expect(err).toMatchObject({ code: 409, message: 'Diese Karte ist schon veröffentlicht.' });
|
||||
expect(api.isAlreadyPublished(err)).toBe(true);
|
||||
});
|
||||
|
||||
it('ein anderes 403 bleibt ein 403 mit dem Servertext', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(403, { error: 'Diese Datei gehört dir nicht.' }));
|
||||
const err = await api.publishCard(input).catch((e: unknown) => e);
|
||||
expect(err).toMatchObject({ code: 403, message: 'Diese Datei gehört dir nicht.' });
|
||||
expect(api.isAlreadyPublished(err)).toBe(false);
|
||||
});
|
||||
|
||||
it('400 (Validierung) behält den Servertext', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(400, { error: 'Bitte gib höchstens 5 Tags an.' }));
|
||||
await expect(api.publishCard(input)).rejects.toMatchObject({ code: 400, message: 'Bitte gib höchstens 5 Tags an.' });
|
||||
});
|
||||
|
||||
it('eine Antwort ohne id ist ein Fehler', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(201, {}));
|
||||
await expect(api.publishCard(input)).rejects.toMatchObject({ code: 503, message: UNAVAILABLE });
|
||||
});
|
||||
|
||||
it('isAlreadyPublished: nur ApiError mit code 409', async () => {
|
||||
const api = await freshApi();
|
||||
expect(api.isAlreadyPublished(new api.ApiError(409, 'x'))).toBe(true);
|
||||
expect(api.isAlreadyPublished(new api.ApiError(400, 'x'))).toBe(false);
|
||||
expect(api.isAlreadyPublished(Object.assign(new Error('x'), { code: 409 }))).toBe(false);
|
||||
expect(api.isAlreadyPublished(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('withdrawCard, reportCard, setVisibility', () => {
|
||||
it('withdrawCard: POST /api/zurueckziehen { id } mit JWT', async () => {
|
||||
const api = await freshApi();
|
||||
await expect(api.withdrawCard('g1')).resolves.toBeUndefined();
|
||||
const [url, init] = fetchMock.mock.calls[0];
|
||||
expect(url).toBe('/api/zurueckziehen');
|
||||
expect(JSON.parse(init.body as string)).toEqual({ id: 'g1' });
|
||||
expect(sentHeaders()['X-Aircard-Jwt']).toBe('jwt1');
|
||||
});
|
||||
|
||||
it('reportCard: POST /api/melden { id, reason } ohne JWT; { schonGemeldet: false } bei neuer Meldung', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(200, { ok: true }));
|
||||
await expect(api.reportCard('g1', 'spam')).resolves.toEqual({ schonGemeldet: false });
|
||||
const [url, init] = fetchMock.mock.calls[0];
|
||||
expect(url).toBe('/api/melden');
|
||||
expect(JSON.parse(init.body as string)).toEqual({ id: 'g1', reason: 'spam' });
|
||||
expect(mocks.createJWT).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reportCard: { schonGemeldet: true } bei doppelter Meldung', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(200, { schonGemeldet: true }));
|
||||
await expect(api.reportCard('g1', 'urheberrecht')).resolves.toEqual({ schonGemeldet: true });
|
||||
});
|
||||
|
||||
it('reportCard: 429 mit dem Servertext', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(429, { error: 'Zu viele Meldungen – bitte später nochmal.' }));
|
||||
await expect(api.reportCard('g1', 'spam')).rejects.toMatchObject({
|
||||
code: 429,
|
||||
message: 'Zu viele Meldungen – bitte später nochmal.',
|
||||
});
|
||||
});
|
||||
|
||||
it('setVisibility: POST /api/admin/sichtbarkeit { id, sichtbar } mit JWT', async () => {
|
||||
const api = await freshApi();
|
||||
await expect(api.setVisibility('g1', false)).resolves.toBeUndefined();
|
||||
await api.setVisibility('g1', true);
|
||||
expect(fetchMock.mock.calls[0][0]).toBe('/api/admin/sichtbarkeit');
|
||||
expect(JSON.parse(fetchMock.mock.calls[0][1].body as string)).toEqual({ id: 'g1', sichtbar: false });
|
||||
expect(JSON.parse(fetchMock.mock.calls[1][1].body as string)).toEqual({ id: 'g1', sichtbar: true });
|
||||
expect(sentHeaders()['X-Aircard-Jwt']).toBe('jwt1');
|
||||
});
|
||||
|
||||
it('setVisibility: 403 für Nicht-Admins mit dem Servertext', async () => {
|
||||
const api = await freshApi();
|
||||
fetchMock.mockResolvedValue(json(403, { error: 'Nur für Admins.' }));
|
||||
await expect(api.setVisibility('g1', true)).rejects.toMatchObject({ code: 403, message: 'Nur für Admins.' });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user