diff --git a/internal/serviceaccount/anomaly_test.go b/internal/serviceaccount/anomaly_test.go index 8adb57ec..03f7127a 100644 --- a/internal/serviceaccount/anomaly_test.go +++ b/internal/serviceaccount/anomaly_test.go @@ -160,23 +160,37 @@ func TestVerifyWithContext_RateSpikeTriggersAuditEvent(t *testing.T) { t.Fatalf("issue token: %v", err) } - // Gewohntes Muster etablieren: mehrere Zugriffe mit spuerbarem Abstand, - // alle aus derselben IP-Range, damit ausschliesslich die Zugriffsrate - // geprueft wird. - for i := 0; i < 4; i++ { - if _, err := store.VerifyWithContext(ctx, plaintext, "", "10.0.0.5"); err != nil { - t.Fatalf("verify (basislinie, durchlauf %d): %v", i, err) - } - if i < 3 { - time.Sleep(120 * time.Millisecond) - } + // Gewohntes Muster etablieren: ein realer Zugriff legt die Zeile an, + // danach wird die Basislinie DIREKT in der DB gesetzt statt ueber + // reale time.Sleep-Abstaende erzeugt zu werden. Grund (2026-09-22, + // Root-Cause-Fix eines unter Host-Last flackernden Tests): bei + // time.Sleep(120ms) lag die Schwelle fuer den anschliessenden + // "Spike"-Zugriff bei baseline/rateSpikeFactor = 24ms — auf einem + // parallel stark ausgelasteten Testhost kann ein einzelner + // VerifyWithContext-Aufruf (Verify + mehrere DB-Round-Trips) diese + // 24ms leicht ueberschreiten, wodurch der Test nicht die + // Testumgebung, sondern eine reale Wettlaufbedingung gegen die + // Host-Last maß. Mit einer Basislinie von 5s (Schwelle 1s) ist der + // Abstand so groß, dass ein einzelner DB-Round-Trip ihn in jeder + // realistischen Lastsituation sicher unterschreitet — deterministisch, + // nicht toleranter gemacht. + if _, err := store.VerifyWithContext(ctx, plaintext, "", "10.0.0.5"); err != nil { + t.Fatalf("verify (basislinie anlegen): %v", err) + } + const seededBaselineSeconds = 5.0 + if _, err := pool.Exec(ctx, ` + UPDATE api_token_rate_state + SET last_seen_at = now(), baseline_interval_seconds = $2, sample_count = $3 + WHERE token_id = $1 + `, tokenID, seededBaselineSeconds, minSamplesForRateBaseline); err != nil { + t.Fatalf("basislinie seeden: %v", err) } if n := countAuditEvents(t, pool, ActionAnomalyRate, tokenID); n != 0 { t.Fatalf("gleichmaessige nutzung soll keinen raten-alarm ausloesen, habe %d event(s)", n) } - // Jetzt ein Buendel von Zugriffen praktisch ohne Abstand — weit unter der - // gewohnten ~120ms-Basislinie (Akzeptanzkriterium 2). + // Jetzt ein Zugriff praktisch ohne Abstand — weit unter der auf 5s + // gesetzten Basislinie, deren Schwelle bei 1s liegt (Akzeptanzkriterium 2). if _, err := store.VerifyWithContext(ctx, plaintext, "", "10.0.0.5"); err != nil { t.Fatalf("verify (rate spike): %v", err) }