import { describe, expect, it } from 'vitest'; import { Drossel, FREIE_VERSUCHE, GRUNDSPERRE_MS, HOECHSTSPERRE_MS, VERGESSEN_NACH_MS, sperreFuer, wartezeitText, } from './drossel'; /** Eine Uhr, die nur weiterläuft, wenn man sie stellt. */ function stehendeUhr(start = 1_000_000) { let zeit = start; return { uhr: () => zeit, vor: (ms: number) => { zeit += ms; }, }; } function scheitern(drossel: Drossel, schluessel: string, mal: number) { for (let i = 0; i < mal; i += 1) drossel.fehlversuch(schluessel); } describe('sperreFuer', () => { it('lässt die ersten Fehlversuche frei', () => { for (let n = 1; n < FREIE_VERSUCHE; n += 1) expect(sperreFuer(n)).toBe(0); }); it('sperrt ab dem fünften Fehlversuch und verdoppelt dann', () => { expect(sperreFuer(FREIE_VERSUCHE)).toBe(GRUNDSPERRE_MS); expect(sperreFuer(FREIE_VERSUCHE + 1)).toBe(2 * GRUNDSPERRE_MS); expect(sperreFuer(FREIE_VERSUCHE + 2)).toBe(4 * GRUNDSPERRE_MS); }); it('bleibt bei der Obergrenze stehen', () => { expect(sperreFuer(FREIE_VERSUCHE + 20)).toBe(HOECHSTSPERRE_MS); }); }); describe('Drossel', () => { it('sperrt nach fünf Fehlversuchen, nicht vorher', () => { const { uhr } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE - 1); expect(drossel.wartezeit('a@x.de')).toBe(0); drossel.fehlversuch('a@x.de'); expect(drossel.wartezeit('a@x.de')).toBe(GRUNDSPERRE_MS); }); it('gibt nach Ablauf der Sperre wieder frei', () => { const { uhr, vor } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE); vor(GRUNDSPERRE_MS); expect(drossel.wartezeit('a@x.de')).toBe(0); }); it('verlängert beim nächsten Fehlversuch nach der Sperre', () => { const { uhr, vor } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE); vor(GRUNDSPERRE_MS); drossel.fehlversuch('a@x.de'); expect(drossel.wartezeit('a@x.de')).toBe(2 * GRUNDSPERRE_MS); }); it('setzt nach erfolgreicher Anmeldung zurück', () => { const { uhr } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE - 1); drossel.erfolg('a@x.de'); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE - 1); expect(drossel.wartezeit('a@x.de')).toBe(0); }); it('vergisst Fehlversuche nach einer Stunde Ruhe', () => { const { uhr, vor } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE - 1); vor(VERGESSEN_NACH_MS); drossel.fehlversuch('a@x.de'); expect(drossel.wartezeit('a@x.de')).toBe(0); }); it('zählt jede Adresse für sich', () => { const { uhr } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'a@x.de', FREIE_VERSUCHE); expect(drossel.wartezeit('a@x.de')).toBeGreaterThan(0); expect(drossel.wartezeit('b@x.de')).toBe(0); }); it('räumt auf, lässt eine laufende Sperre aber nicht verdrängen', () => { const { uhr } = stehendeUhr(); const drossel = new Drossel(uhr); scheitern(drossel, 'opfer@x.de', FREIE_VERSUCHE); for (let i = 0; i < 10_050; i += 1) drossel.fehlversuch(`erfunden-${i}@x.de`); expect(drossel.groesse).toBeLessThanOrEqual(10_001); expect(drossel.wartezeit('opfer@x.de')).toBe(GRUNDSPERRE_MS); }); }); describe('wartezeitText', () => { it('rundet auf ganze Minuten auf', () => { expect(wartezeitText(1)).toBe('in einer Minute'); expect(wartezeitText(60_000)).toBe('in einer Minute'); expect(wartezeitText(60_001)).toBe('in 2 Minuten'); expect(wartezeitText(HOECHSTSPERRE_MS)).toBe('in 15 Minuten'); }); });