feat(archive): BAK-08 Checksum-basierte Objekt-Integritaetspruefung
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.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package scrub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func requireFileRevisionsFixture(t *testing.T) (pool *pgxpool.Pool, userID, docID string) {
|
||||
t.Helper()
|
||||
p := requireTestPool(t)
|
||||
ctx := context.Background()
|
||||
if _, err := p.Exec(ctx, `
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT NOT NULL UNIQUE, name TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS documents (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL,
|
||||
created_by UUID NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_revisions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
||||
storage_key TEXT NOT NULL, checksum_sha256 TEXT NOT NULL, size_bytes BIGINT NOT NULL,
|
||||
mime_type TEXT NOT NULL, revision_number INTEGER NOT NULL, created_by UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
`); err != nil {
|
||||
t.Fatalf("file_revisions-fixture: %v", err)
|
||||
}
|
||||
var uid string
|
||||
if err := p.QueryRow(ctx, `INSERT INTO users (email, name) VALUES ('scrub-test@example.test', 'Test') RETURNING id`).Scan(&uid); err != nil {
|
||||
t.Fatalf("testbenutzer anlegen: %v", err)
|
||||
}
|
||||
var did string
|
||||
if err := p.QueryRow(ctx, `INSERT INTO documents (title, created_by) VALUES ('doc', $1) RETURNING id`, uid).Scan(&did); err != nil {
|
||||
t.Fatalf("testdokument anlegen: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _, _ = p.Exec(context.Background(), `TRUNCATE file_revisions, documents, users CASCADE`) })
|
||||
return p, uid, did
|
||||
}
|
||||
|
||||
// TestActualChecksum_MatchesRealFileContent ist Nachweis, dass
|
||||
// ActualChecksum tatsaechlich den Dateiinhalt liest und hasht (kein
|
||||
// Header-/Groessenvergleich).
|
||||
func TestActualChecksum_MatchesRealFileContent(t *testing.T) {
|
||||
baseDir := t.TempDir()
|
||||
content := []byte("echter dateiinhalt fuer scrub-test")
|
||||
path := filepath.Join(baseDir, "documents", "x", "revisions", "1")
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, content, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ActualChecksum(baseDir, "documents/x/revisions/1")
|
||||
if err != nil {
|
||||
t.Fatalf("actualChecksum: %v", err)
|
||||
}
|
||||
sum := sha256.Sum256(content)
|
||||
want := hex.EncodeToString(sum[:])
|
||||
if got != want {
|
||||
t.Fatalf("checksum = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestExpectedChecksums_ReadsRealFileRevisions ist Nachweis gegen echtes
|
||||
// Postgres, kein Mock.
|
||||
func TestExpectedChecksums_ReadsRealFileRevisions(t *testing.T) {
|
||||
pool, uid, did := requireFileRevisionsFixture(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO file_revisions (document_id, storage_key, checksum_sha256, size_bytes, mime_type, revision_number, created_by)
|
||||
VALUES ($1, 'documents/x/revisions/1', 'abc123', 10, 'text/plain', 1, $2)
|
||||
`, did, uid); err != nil {
|
||||
t.Fatalf("testrevision anlegen: %v", err)
|
||||
}
|
||||
|
||||
got, err := ExpectedChecksums(ctx, pool, []string{"documents/x/revisions/1", "documents/fehlt/revisions/1"})
|
||||
if err != nil {
|
||||
t.Fatalf("expectedChecksums: %v", err)
|
||||
}
|
||||
if len(got) != 1 || got["documents/x/revisions/1"] != "abc123" {
|
||||
t.Fatalf("unerwartetes ergebnis: %+v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user