import { describe, expect, it } from 'vitest'; import { errorMessage, errorType, isInvalidLinkError, isRowMissing, isUnauthorized, type ErrorContext } from './errors'; /** Fehler wie vom SDK (AppwriteException): Felder code, type, message. */ const fail = (code: number, type: string, message = '') => ({ code, type, message }); const LINK = 'Der Link ist ungültig oder abgelaufen.'; const WRONG = 'E-Mail oder Passwort ist falsch.'; const LENGTH = 'Das Passwort muss 8 bis 256 Zeichen lang sein.'; const COMMON = 'Dieses Passwort ist zu verbreitet. Bitte wähle ein anderes.'; const EMAIL = 'Bitte gib eine gültige E-Mail-Adresse ein.'; const GENERIC = 'Etwas ist schiefgelaufen. Bitte nochmal versuchen.'; const PW_LENGTH = 'Invalid `password` param: Password must be between 8 and 256 characters long.'; const PW_COMMON = 'Invalid `password` param: Password must be at least 8 characters and should not be one of the commonly used password.'; const EMAIL_INVALID = 'Invalid `email` param: Value must be a valid email address'; describe('errorMessage nach type', () => { it('user_invalid_credentials', () => { expect(errorMessage(fail(401, 'user_invalid_credentials', 'Invalid credentials.'), 'login')).toBe(WRONG); }); it('user_already_exists', () => { expect(errorMessage(fail(409, 'user_already_exists'), 'register')).toBe( 'Diese E-Mail ist schon registriert. Melde dich an.', ); }); it('user_invalid_token', () => { expect(errorMessage(fail(401, 'user_invalid_token'), 'reset')).toBe(LINK); }); it('user_not_found hängt vom Kontext ab', () => { expect(errorMessage(fail(404, 'user_not_found'), 'recover')).toBe('Zu dieser E-Mail gibt es kein Konto.'); expect(errorMessage(fail(404, 'user_not_found'), 'reset')).toBe(LINK); }); it('user_not_found in anderen Kontexten fällt auf die allgemeine Meldung zurück', () => { expect(errorMessage(fail(404, 'user_not_found'), 'save')).toBe(GENERIC); }); it('row_not_found', () => { expect(errorMessage(fail(404, 'row_not_found'), 'load')).toBe( 'Diese Karte gibt es nicht (mehr) oder sie gehört nicht zu deinem Konto.', ); }); it('storage_file_not_found', () => { expect(errorMessage(fail(404, 'storage_file_not_found'), 'load')).toBe('Das Bild dieser Karte fehlt.'); }); it('general_unknown_origin', () => { expect(errorMessage(fail(403, 'general_unknown_origin'), 'login')).toBe( 'Diese Seite ist beim Server nicht freigeschaltet. Bitte melde dich beim Betreiber.', ); }); it('user_session_already_exists: Hinweis mit Handlungsanweisung', () => { expect(errorMessage(fail(401, 'user_session_already_exists'), 'login')).toBe( 'Du bist schon angemeldet. Bitte lade die Seite neu.', ); expect(errorMessage(fail(401, 'user_session_already_exists'), 'register')).toBe( 'Du bist schon angemeldet. Bitte lade die Seite neu.', ); }); it('user_count_exceeded', () => { expect(errorMessage(fail(501, 'user_count_exceeded'), 'register')).toBe('Zurzeit sind keine neuen Konten möglich.'); }); }); describe('errorMessage bei general_argument_invalid', () => { const invalid = (message: string) => fail(400, 'general_argument_invalid', message); it('email', () => { for (const ctx of ['login', 'register', 'recover'] as ErrorContext[]) { expect(errorMessage(invalid(EMAIL_INVALID), ctx)).toBe(EMAIL); } }); it('password beim Login heißt: falsche Zugangsdaten', () => { expect(errorMessage(invalid(PW_LENGTH), 'login')).toBe(WRONG); }); it('password sonst: 8 bis 256 Zeichen', () => { expect(errorMessage(invalid(PW_LENGTH), 'register')).toBe(LENGTH); expect(errorMessage(invalid(PW_LENGTH), 'reset')).toBe(LENGTH); }); it('password sonst: zu verbreitet', () => { expect(errorMessage(invalid(PW_COMMON), 'register')).toBe(COMMON); expect(errorMessage(invalid(PW_COMMON), 'reset')).toBe(COMMON); }); it('userId und secret: Link ungültig', () => { expect(errorMessage(invalid('Invalid `userId` param: Parameter must be a valid number'), 'reset')).toBe(LINK); expect(errorMessage(invalid('Invalid `secret` param: Value must be a valid string'), 'reset')).toBe(LINK); }); it('unbekannter oder fehlender Parameter: allgemeine Meldung', () => { expect(errorMessage(invalid('Invalid `url` param: Invalid URI'), 'recover')).toBe(GENERIC); expect(errorMessage(invalid('Something is wrong'), 'register')).toBe(GENERIC); }); }); describe('errorMessage nach code und Fehlerart', () => { it('429', () => { expect(errorMessage(fail(429, 'general_rate_limit_exceeded'), 'login')).toBe('Zu viele Versuche – bitte kurz warten.'); expect(errorMessage(fail(429, ''), 'recover')).toBe('Zu viele Versuche – bitte kurz warten.'); }); it('type geht vor code', () => { expect(errorMessage(fail(429, 'user_invalid_credentials'), 'login')).toBe(WRONG); }); it('TypeError ist ein Netzfehler', () => { expect(errorMessage(new TypeError('Failed to fetch'), 'save')).toBe('Keine Verbindung zum Server.'); }); it('alles andere ist die allgemeine Meldung', () => { expect(errorMessage(fail(500, 'general_server_error', 'boom'), 'save')).toBe(GENERIC); expect(errorMessage(new Error('x'), 'load')).toBe(GENERIC); expect(errorMessage(undefined, 'load')).toBe(GENERIC); expect(errorMessage('text', 'load')).toBe(GENERIC); expect(errorMessage(null, 'load')).toBe(GENERIC); }); }); describe('isUnauthorized', () => { it('erkennt Code 401', () => { expect(isUnauthorized({ code: 401 })).toBe(true); }); it('andere Codes und Nicht-Fehler nicht', () => { expect(isUnauthorized({ code: 404 })).toBe(false); expect(isUnauthorized(new TypeError('Failed to fetch'))).toBe(false); expect(isUnauthorized(null)).toBe(false); expect(isUnauthorized(undefined)).toBe(false); }); }); describe('errorType', () => { it('liefert den type-String oder null', () => { expect(errorType(fail(409, 'user_already_exists'))).toBe('user_already_exists'); expect(errorType(fail(500, ''))).toBeNull(); expect(errorType(new TypeError('x'))).toBeNull(); expect(errorType(undefined)).toBeNull(); }); }); describe('isRowMissing', () => { it('404 mit row_not_found heißt: die Zeile gibt es nicht (oder nicht für mich)', () => { expect(isRowMissing(fail(404, 'row_not_found'))).toBe(true); }); it('404 ohne Typ (leer oder fehlend) zählt ebenso', () => { expect(isRowMissing(fail(404, ''))).toBe(true); expect(isRowMissing({ code: 404 })).toBe(true); }); it('eine fehlende Tabelle, Datenbank oder Datei ist ein echter Fehler, kein „Zeile weg“', () => { for (const type of ['table_not_found', 'database_not_found', 'storage_file_not_found']) { expect(isRowMissing(fail(404, type)), type).toBe(false); } }); it('andere Status und Nicht-Fehler nicht', () => { expect(isRowMissing(fail(401, 'row_not_found'))).toBe(false); expect(isRowMissing(fail(500, 'row_not_found'))).toBe(false); expect(isRowMissing({ code: '404', type: 'row_not_found' })).toBe(false); expect(isRowMissing(new TypeError('Failed to fetch'))).toBe(false); expect(isRowMissing('row_not_found')).toBe(false); expect(isRowMissing(null)).toBe(false); expect(isRowMissing(undefined)).toBe(false); }); }); describe('isInvalidLinkError (Passwort-neu-Seite)', () => { it('Link-Fehler', () => { expect(isInvalidLinkError(fail(401, 'user_invalid_token'))).toBe(true); expect(isInvalidLinkError(fail(404, 'user_not_found'))).toBe(true); expect(isInvalidLinkError(fail(400, 'general_argument_invalid', 'Invalid `userId` param: x'))).toBe(true); expect(isInvalidLinkError(fail(400, 'general_argument_invalid', 'Invalid `secret` param: x'))).toBe(true); }); it('Passwort- und sonstige Fehler gehören zum Feld', () => { expect(isInvalidLinkError(fail(400, 'general_argument_invalid', PW_LENGTH))).toBe(false); expect(isInvalidLinkError(fail(429, 'general_rate_limit_exceeded'))).toBe(false); expect(isInvalidLinkError(new TypeError('Failed to fetch'))).toBe(false); expect(isInvalidLinkError(undefined)).toBe(false); }); });