import { createRequire } from 'node:module'; import { describe, expect, it } from 'vitest'; import { API_MESSAGES, apiMessage } from './api-messages'; import { EN } from './en'; import { setLang } from './i18n'; // MSG, alle Fehlertexte der API, liegt im Dienst (deploy/api/common.js, CommonJS ohne Abhängigkeiten); der Test lädt es von dort. const { MSG } = createRequire(import.meta.url)('../../../deploy/api/common.js') as { MSG: Record }; const GENERIC_EN = 'Something went wrong. Please try again.'; describe('Gleichlauf mit MSG im API-Dienst (deploy/api/common.js)', () => { it('der Dienst liefert seine Texte (sonst bestünde der Vergleich leer)', () => { expect(Object.keys(MSG).length).toBeGreaterThan(30); expect(MSG.alreadyPublished).toBe('Diese Karte ist schon veröffentlicht.'); }); it('jeder Schlüssel von MSG steht mit demselben deutschen Text in API_MESSAGES', () => { const different = Object.keys(MSG).filter((key) => API_MESSAGES[key] !== MSG[key]); expect(different).toEqual([]); }); it('darüber hinaus hat API_MESSAGES nur die Client-Codes generic und jwtUnsupported', () => { const extra = Object.keys(API_MESSAGES).filter((key) => !Object.hasOwn(MSG, key)); expect(extra.sort()).toEqual(['generic', 'jwtUnsupported']); }); it('jeder Text hat eine englische Übersetzung, die vom Deutschen abweicht', () => { const untranslated = Object.values(API_MESSAGES).filter((text) => !Object.hasOwn(EN, text) || EN[text] === text); expect(untranslated).toEqual([]); }); }); describe('apiMessage: bekannter Code', () => { it('Deutsch: der Text des Codes', () => { expect(apiMessage('alreadyPublished', 409, 'Diese Karte ist schon veröffentlicht.')).toBe( 'Diese Karte ist schon veröffentlicht.', ); expect(apiMessage('logoGone', 404, 'Dieses Logo ist nicht (mehr) verfügbar.')).toBe('Dieses Logo ist nicht (mehr) verfügbar.'); }); it('Englisch: die Übersetzung des Codes', () => { setLang('en'); expect(apiMessage('alreadyPublished', 409, 'Diese Karte ist schon veröffentlicht.')).toBe('This card is already published.'); expect(apiMessage('logoGone', 404, 'Dieses Logo ist nicht (mehr) verfügbar.')).toBe('This logo is no longer available.'); expect(apiMessage('unavailable', 503, 'Der Dienst ist gerade nicht erreichbar.')).toBe('The service is currently unavailable.'); }); it('jeder Code in beiden Sprachen: deutsch wie im Wörterbuch, englisch laut en.ts', () => { for (const [code, text] of Object.entries(API_MESSAGES)) { setLang('de'); expect(apiMessage(code, 400, text)).toBe(text); setLang('en'); expect(apiMessage(code, 400, text)).toBe(EN[text]); } }); it('Englisch: der Code entscheidet, nicht der mitgelieferte Text und nicht der Status', () => { setLang('en'); expect(apiMessage('logoGone', 500, 'irgendein anderer Text')).toBe('This logo is no longer available.'); expect(apiMessage('logoGone', 404, '')).toBe('This logo is no longer available.'); }); it('die Sprache wird bei jedem Aufruf neu gelesen', () => { expect(apiMessage('logoGone', 404, 'x')).toBe('Dieses Logo ist nicht (mehr) verfügbar.'); setLang('en'); expect(apiMessage('logoGone', 404, 'x')).toBe('This logo is no longer available.'); setLang('de'); expect(apiMessage('logoGone', 404, 'x')).toBe('Dieses Logo ist nicht (mehr) verfügbar.'); }); it('die Client-Codes generic und jwtUnsupported werden wie die des Dienstes übersetzt', () => { setLang('en'); expect(apiMessage('generic', 500, 'x')).toBe(GENERIC_EN); expect(apiMessage('jwtUnsupported', 501, 'x')).toBe( 'Token sign-in is turned off on the server – please let the site owner know.', ); }); }); describe('apiMessage: dynamische Prüftexte (Code badRequest)', () => { const dynamic = 'Der Titel ist zu lang (höchstens 60 Zeichen).'; it('Deutsch: der genauere Servertext statt „Ungültige Anfrage.“', () => { expect(apiMessage('badRequest', 400, dynamic)).toBe(dynamic); expect(apiMessage('badRequest', 400, 'Ungültige Anfrage.')).toBe('Ungültige Anfrage.'); }); it('Englisch: der allgemeine Text des Codes (der deutsche Prüftext lässt sich nicht übersetzen)', () => { setLang('en'); expect(apiMessage('badRequest', 400, dynamic)).toBe('Invalid request.'); expect(apiMessage('badRequest', 400, 'Ungültige Anfrage.')).toBe('Invalid request.'); }); }); describe('apiMessage: unbekannter Code', () => { it.each([null, 'error', 'neuerCode', ''])('Deutsch (%j): der Servertext', (code) => { expect(apiMessage(code, 500, 'Der Server sagt etwas.')).toBe('Der Server sagt etwas.'); expect(apiMessage(code, 400, 'Der Server sagt etwas.')).toBe('Der Server sagt etwas.'); }); it.each([ [400, 'Invalid request.'], [403, 'You are not allowed to do that.'], [404, 'Not found.'], [401, GENERIC_EN], [409, GENERIC_EN], [429, GENERIC_EN], [500, GENERIC_EN], [503, GENERIC_EN], ] as const)('Englisch: Status %i → „%s“', (status, expected) => { setLang('en'); for (const code of [null, 'error', 'neuerCode']) expect(apiMessage(code, status, 'Der Server sagt etwas.')).toBe(expected); }); it.each(['constructor', 'toString', '__proto__', 'hasOwnProperty'])( 'Namen aus Object.prototype (%s) sind kein Code', (code) => { expect(apiMessage(code, 500, 'Servertext')).toBe('Servertext'); setLang('en'); expect(apiMessage(code, 500, 'Servertext')).toBe(GENERIC_EN); }, ); });