Stichprobenbasierter Scrub-Job: nimmt BAK-05s existing_in_storage, priorisiert nach eigenem scrub_state.last_scrubbed_at (nicht file_revisions.created_at, sonst kein echtes Rotationsverhalten), prueft Inhalt per SHA-256 gegen file_revisions.checksum_sha256. Meldung ueber echten dauerhaften /metrics-Endpunkt (Pull-Modell, OPS-03 scrapt, kein Push), Counter monoton steigend. Real registriert in Core metrics_sources, End-zu-Ende ueber OPS-03-Aggregator bestaetigt, realer Befund-Durchlauf mit absichtlich falscher Pruefsumme durchgefuehrt.
93 lines
2.8 KiB
Go
93 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|