generated from knso/webklar-preview-template
Release-Vorbereitung: Proxy, Kostenoptimierung, Freemium, Store-Assets
- Backend-Proxy (Cloudflare Worker in proxy/): API-Key serverseitig, Modell-Whitelist, max_tokens-Deckel, Tageslimits pro Gerät und IP - Modell-Split: Analyse auf Sonnet 5, Übersetzung/Antwort auf Haiku 4.5 - Bild-Downscaling auf 2000px vor dem Upload (ScanScreen) - Expo SDK 56 -> 57 Upgrade - Freemium: 3 Gratis-Analysen mit lokalem Zähler und Paywall-Hinweis - Einstellungen: API-Key-Feld nur noch im Dev-Modus (NUTZT_PROXY) - Build-Setup: Bundle-IDs, eas.json, Splash-Screen-Plugin - Tests: jest-expo + 17 Tests für die Ampel-/Fristlogik - Rechtliches: Datenschutzerklärung (Entwurf), Webseite (webseite/, live auf behoerdenklar.pages.dev), Store-Texte (store/) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
88c49e1b54
commit
9dc1d8d87b
26 files changed
+6587
-480
No files matched your search
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Tests für die Dringlichkeits-Ampel — die kritischste Logik der App:
|
||||
* Eine falsche Ampel könnte dazu führen, dass ein Nutzer eine Behörden-Frist
|
||||
* verpasst. Deshalb hier alle Grenzfälle deterministisch abgesichert.
|
||||
*/
|
||||
import { berechneAmpel, tageBis } from '../ampel';
|
||||
import { BriefAnalyse } from '../../types';
|
||||
|
||||
/** Fixes "Heute" für alle Tests: 15.07.2026 (Mitte des Monats, kein Randfall). */
|
||||
const HEUTE = new Date('2026-07-15T10:30:00');
|
||||
|
||||
/** Minimal gültige Analyse; frist/termin pro Test überschreiben. */
|
||||
function analyse(teile: Partial<BriefAnalyse>): BriefAnalyse {
|
||||
return {
|
||||
brieftyp: 'Test',
|
||||
absender: 'Testamt',
|
||||
kernaussage: '',
|
||||
erklaerung_einfach: '',
|
||||
fachbegriffe: [],
|
||||
frist: null,
|
||||
termin: null,
|
||||
checkliste: [],
|
||||
antwort_noetig: false,
|
||||
antwort_optionen: [],
|
||||
...teile,
|
||||
};
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(HEUTE);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe('tageBis', () => {
|
||||
it('liefert 0 für heute', () => {
|
||||
expect(tageBis('2026-07-15')).toBe(0);
|
||||
});
|
||||
|
||||
it('liefert positive Tage für die Zukunft', () => {
|
||||
expect(tageBis('2026-07-16')).toBe(1);
|
||||
expect(tageBis('2026-07-22')).toBe(7);
|
||||
});
|
||||
|
||||
it('liefert negative Tage für die Vergangenheit', () => {
|
||||
expect(tageBis('2026-07-14')).toBe(-1);
|
||||
expect(tageBis('2026-07-01')).toBe(-14);
|
||||
});
|
||||
|
||||
it('zählt ganze Tage unabhängig von der Uhrzeit "jetzt"', () => {
|
||||
// 10:30 Uhr darf keine halben Tage erzeugen
|
||||
expect(tageBis('2026-07-18')).toBe(3);
|
||||
});
|
||||
|
||||
it('funktioniert über Monatsgrenzen hinweg', () => {
|
||||
expect(tageBis('2026-08-01')).toBe(17);
|
||||
});
|
||||
});
|
||||
|
||||
describe('berechneAmpel — Stufen', () => {
|
||||
it('grün ohne Frist und Termin', () => {
|
||||
const status = berechneAmpel(analyse({}));
|
||||
expect(status.stufe).toBe('gruen');
|
||||
expect(status.text).toContain('Keine Frist');
|
||||
});
|
||||
|
||||
it('rot bei abgelaufener Frist (inkl. Hinweis)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-07-10', aktion: 'Unterlagen einreichen' } })
|
||||
);
|
||||
expect(status.stufe).toBe('rot');
|
||||
expect(status.text).toContain('abgelaufen');
|
||||
});
|
||||
|
||||
it('rot bei Frist heute', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-07-15', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.stufe).toBe('rot');
|
||||
expect(status.text).toContain('HEUTE');
|
||||
});
|
||||
|
||||
it('rot bei Frist in 6 Tagen (< 7)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-07-21', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.stufe).toBe('rot');
|
||||
});
|
||||
|
||||
it('gelb bei Frist in genau 7 Tagen (Grenzwert)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-07-22', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.stufe).toBe('gelb');
|
||||
});
|
||||
|
||||
it('gelb bei Frist in genau 21 Tagen (Grenzwert)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-08-05', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.stufe).toBe('gelb');
|
||||
});
|
||||
|
||||
it('grün bei Frist in 22 Tagen (> 21)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-08-06', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.stufe).toBe('gruen');
|
||||
});
|
||||
});
|
||||
|
||||
describe('berechneAmpel — Frist und Termin kombiniert', () => {
|
||||
it('das dringendste Datum bestimmt die Ampel (Termin näher als Frist)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({
|
||||
frist: { datum: '2026-08-20', aktion: 'Unterlagen' }, // 36 Tage → grün
|
||||
termin: { datum: '2026-07-18', uhrzeit: '09:30', ort: 'Raum 1' }, // 3 Tage → rot
|
||||
})
|
||||
);
|
||||
expect(status.stufe).toBe('rot');
|
||||
expect(status.text).toContain('Termin');
|
||||
});
|
||||
|
||||
it('das dringendste Datum bestimmt die Ampel (Frist näher als Termin)', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({
|
||||
frist: { datum: '2026-07-25', aktion: 'Antwort' }, // 10 Tage → gelb
|
||||
termin: { datum: '2026-09-01', uhrzeit: null, ort: null }, // weit weg
|
||||
})
|
||||
);
|
||||
expect(status.stufe).toBe('gelb');
|
||||
expect(status.text).toContain('Frist');
|
||||
});
|
||||
|
||||
it('nur Termin ohne Frist funktioniert', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ termin: { datum: '2026-07-16', uhrzeit: null, ort: null } })
|
||||
);
|
||||
expect(status.stufe).toBe('rot');
|
||||
expect(status.text).toContain('Termin');
|
||||
});
|
||||
});
|
||||
|
||||
describe('berechneAmpel — Textdetails', () => {
|
||||
it('Einzahl bei 1 Tag ("in 1 Tag", nicht "Tagen")', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-07-16', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.text).toContain('in 1 Tag ');
|
||||
});
|
||||
|
||||
it('Einzahl bei 1 Tag abgelaufen', () => {
|
||||
const status = berechneAmpel(
|
||||
analyse({ frist: { datum: '2026-07-14', aktion: 'Antwort' } })
|
||||
);
|
||||
expect(status.text).toContain('vor 1 Tag)');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user