Prüfgate für Archivierung & Suche: verbindliche Kriterien für
Verschlüsselung, Speicherpfad-Konventionen und Suchqualität.
- qagate/gate.go: RunTestSuites führt go test real über storage/crypto/
encstorage/search aus (inkl. ARC-08-Schlüsselrotation, SRC-10-OCR-
Konfidenz). ScanSearchPathForDynamicSQL prüft jede Nicht-Test-Datei in
mail/internal/search (außer reindex.go, dokumentierte DDL-Ausnahme)
auf tatsächliche fmt.Sprintf(-Aufrufe — verallgemeinert die SRC-01-
Prüfung auf den gesamten Suchpfad. GateResult.Report() liefert
dokumentierten, zeitgestempelten Bericht.
- Echten Fehlalarm gefunden und behoben: Kommentartext in fields.go
("...fmt.Sprintf/strings.Join...") wurde fälschlich als Verstoß
erkannt — Suchmuster auf "fmt.Sprintf(" präzisiert.
Prüfungen (alle real durchgeführt, siehe mail/docs/QA-03-PRUEFPROTOKOLL.md):
1. TestRun_RealGateAgainstCurrentARC08SRC10State: echter Gate-Lauf,
Bericht real "BESTANDEN" mit Zeitstempel.
2. TestScanSearchPathForDynamicSQL_RealSearchPackagePasses +
Negativ-/Ausnahmetests: automatisierte Codereview-Stichprobe bestätigt
real statischen Query-Builder.
3. Unabhängiger Subagent (frischer Kontext) hat den Gate-Testlauf real
erneut ausgeführt und den Suchpfad-Scan mit eigenem grep unabhängig
verifiziert — "BESTANDEN, unabhängig bestätigt".
Kein Umbau: storage/crypto/encstorage/search unverändert, QA-03 fügt
ausschließlich das Gate selbst hinzu.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
113 lines
4.1 KiB
Go
113 lines
4.1 KiB
Go
package qagate
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func moduleRoot(t *testing.T) string {
|
|
t.Helper()
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("arbeitsverzeichnis ermitteln: %v", err)
|
|
}
|
|
// gate_test.go liegt in internal/qagate — Modulwurzel ist zwei Ebenen
|
|
// darüber.
|
|
return filepath.Join(wd, "..", "..")
|
|
}
|
|
|
|
// TestScanSearchPathForDynamicSQL_RealSearchPackagePasses ist die
|
|
// geforderte Pflichtprüfung 2: Codereview-Stichprobe bestätigt
|
|
// statischen Query-Builder — automatisiert und reproduzierbar gegen den
|
|
// echten mail/internal/search-Quelltext ausgeführt statt rein manuell
|
|
// behauptet.
|
|
func TestScanSearchPathForDynamicSQL_RealSearchPackagePasses(t *testing.T) {
|
|
root := moduleRoot(t)
|
|
result, err := ScanSearchPathForDynamicSQL(filepath.Join(root, "internal", "search"))
|
|
if err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
if !result.Passed {
|
|
t.Fatalf("erwartete bestandenen scan, habe verstöße: %v", result.Violations)
|
|
}
|
|
}
|
|
|
|
// TestScanSearchPathForDynamicSQL_DetectsRealViolation beweist, dass der
|
|
// Scanner einen tatsächlichen Verstoß auch real erkennt (Negativtest,
|
|
// analog zu no_dynamic_sql_test.go) — ein Gate, das nie einen Verstoß
|
|
// melden kann, wäre wertlos.
|
|
func TestScanSearchPathForDynamicSQL_DetectsRealViolation(t *testing.T) {
|
|
dir := t.TempDir()
|
|
violatingFile := "bad_query.go"
|
|
content := []byte("package search\n\nimport \"fmt\"\n\nfunc bad(field string) string {\n\treturn fmt.Sprintf(\"WHERE %s = 1\", field)\n}\n")
|
|
if err := os.WriteFile(filepath.Join(dir, violatingFile), content, 0o600); err != nil {
|
|
t.Fatalf("testdatei schreiben: %v", err)
|
|
}
|
|
|
|
result, err := ScanSearchPathForDynamicSQL(dir)
|
|
if err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
if result.Passed {
|
|
t.Fatal("erwartete erkannten verstoß, scan meldet bestanden")
|
|
}
|
|
if len(result.Violations) != 1 || result.Violations[0] != violatingFile+": enthält fmt.Sprintf(" {
|
|
t.Fatalf("unerwartetes verstoßergebnis: %+v", result.Violations)
|
|
}
|
|
}
|
|
|
|
// TestScanSearchPathForDynamicSQL_ExemptsDocumentedDDLFile bestätigt,
|
|
// dass die dokumentierte Ausnahme (reindex.go, Schema-/DDL-Verwaltung,
|
|
// kein Abfragepfad) NICHT als Verstoß gewertet wird.
|
|
func TestScanSearchPathForDynamicSQL_ExemptsDocumentedDDLFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
content := []byte("package search\n\nimport \"fmt\"\n\nfunc ddl(name string) string {\n\treturn fmt.Sprintf(\"CREATE TABLE %s (...)\", name)\n}\n")
|
|
if err := os.WriteFile(filepath.Join(dir, "reindex.go"), content, 0o600); err != nil {
|
|
t.Fatalf("testdatei schreiben: %v", err)
|
|
}
|
|
|
|
result, err := ScanSearchPathForDynamicSQL(dir)
|
|
if err != nil {
|
|
t.Fatalf("scan: %v", err)
|
|
}
|
|
if !result.Passed {
|
|
t.Fatalf("erwartete bestandenen scan (dokumentierte ausnahme), habe: %v", result.Violations)
|
|
}
|
|
}
|
|
|
|
// TestRun_RealGateAgainstCurrentARC08SRC10State ist die geforderte
|
|
// Pflichtprüfung 1: Gate-Lauf gegen aktuellen Stand von ARC-08/SRC-10
|
|
// dokumentiert — führt das vollständige Gate real gegen den aktuellen
|
|
// Modul-Quelltext aus (inklusive echter Postgres-/Manticore-
|
|
// Integrationstests, darunter ARC-08s Schlüsselrotation und SRC-10s
|
|
// OCR-Konfidenzabfrage) und dokumentiert das Ergebnis mit Zeitstempel.
|
|
func TestRun_RealGateAgainstCurrentARC08SRC10State(t *testing.T) {
|
|
if os.Getenv("TEST_TENANT_DSN") == "" || os.Getenv("TEST_MANTICORE_URL") == "" {
|
|
t.Skip("TEST_TENANT_DSN/TEST_MANTICORE_URL nicht gesetzt, Integrationstest übersprungen")
|
|
}
|
|
root := moduleRoot(t)
|
|
ctx := context.Background()
|
|
|
|
result, err := Run(ctx, root)
|
|
if err != nil {
|
|
t.Fatalf("gate-lauf: %v", err)
|
|
}
|
|
if result.Timestamp.IsZero() {
|
|
t.Fatal("erwarteter zeitstempel fehlt")
|
|
}
|
|
if !result.SQLScan.Passed {
|
|
t.Fatalf("statischer scan fehlgeschlagen: %v", result.SQLScan.Violations)
|
|
}
|
|
if !result.TestSuite.Passed {
|
|
t.Fatalf("testsuiten fehlgeschlagen, ausgabe:\n%s", result.TestSuite.Output)
|
|
}
|
|
|
|
reportPath := filepath.Join(t.TempDir(), "qa-03-gate-report.md")
|
|
if err := os.WriteFile(reportPath, []byte(result.Report()), 0o600); err != nil {
|
|
t.Fatalf("bericht schreiben: %v", err)
|
|
}
|
|
t.Logf("Gate-Bericht (%s):\n%s", reportPath, result.Report())
|
|
}
|