package scrub import ( "context" "os" "testing" "time" "github.com/jackc/pgx/v5/pgxpool" ) func requireTestPool(t *testing.T) *pgxpool.Pool { t.Helper() dsn := os.Getenv("TEST_TENANT_DSN") if dsn == "" { t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest uebersprungen") } ctx := context.Background() pool, err := pgxpool.New(ctx, dsn) if err != nil { t.Fatalf("pool: %v", err) } t.Cleanup(func() { pool.Close() }) if _, err := pool.Exec(ctx, ` CREATE TABLE IF NOT EXISTS scrub_state ( storage_key TEXT PRIMARY KEY, last_scrubbed_at TIMESTAMPTZ NOT NULL, last_result TEXT NOT NULL CHECK (last_result IN ('ok', 'failed')) ); CREATE TABLE IF NOT EXISTS scrub_counters ( id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1), findings_total BIGINT NOT NULL DEFAULT 0 ); INSERT INTO scrub_counters (id, findings_total) VALUES (1, 0) ON CONFLICT (id) DO NOTHING; `); err != nil { t.Fatalf("schema: %v", err) } t.Cleanup(func() { _, _ = pool.Exec(context.Background(), `TRUNCATE scrub_state; UPDATE scrub_counters SET findings_total = 0 WHERE id = 1`) }) return pool } // TestMarkScrubbed_IsIdempotent ist Nachweis fuer "Lauf ist idempotent und // unterbrechbar ohne inkonsistenten Zustand": derselbe storage_key kann // beliebig oft neu markiert werden, es entsteht kein Duplikat/Fehler. func TestMarkScrubbed_IsIdempotent(t *testing.T) { pool := requireTestPool(t) ctx := context.Background() key := "documents/x/revisions/1" if err := MarkScrubbed(ctx, pool, key, true, time.Now().UTC()); err != nil { t.Fatalf("erster markScrubbed: %v", err) } second := time.Now().UTC().Add(time.Hour) if err := MarkScrubbed(ctx, pool, key, false, second); err != nil { t.Fatalf("zweiter markScrubbed (ueberschreibt): %v", err) } last, err := LoadLastScrubbed(ctx, pool) if err != nil { t.Fatalf("loadLastScrubbed: %v", err) } if len(last) != 1 { t.Fatalf("erwartet genau 1 eintrag (kein duplikat), habe %d", len(last)) } // Postgres timestamptz rundet auf Mikrosekunden, Go time.Time hat // Nanosekunden-Praezision - Vergleich daher auf Mikrosekunden gerundet. if !last[key].Truncate(time.Microsecond).Equal(second.Truncate(time.Microsecond)) { t.Fatalf("last_scrubbed_at nicht ueberschrieben: %v, want %v", last[key], second) } } // TestRecordFinding_IsMonotonicallyIncreasing ist Nachweis, dass der // Zaehler ein gueltiger Prometheus-Counter ist (steigt nur, sinkt nie). func TestRecordFinding_IsMonotonicallyIncreasing(t *testing.T) { pool := requireTestPool(t) ctx := context.Background() for i := 0; i < 3; i++ { if err := RecordFinding(ctx, pool); err != nil { t.Fatalf("recordFinding: %v", err) } } total, err := FindingsTotal(ctx, pool) if err != nil { t.Fatalf("findingsTotal: %v", err) } if total != 3 { t.Fatalf("erwartet 3, habe %d", total) } }