SRC-01: manticore-suchindex-fuer-mails
Manticore-RT-Index für Mail-Suchdokumente (Betreff, Text, Anhangstext, Metadaten), statisches versioniertes Schema. - fields.go: statische Feld-/Index-Namen-Whitelist, einzige Quelle für Feldnamen im Paket (vermeidet known-issues-archivmail.md #11/#12: Sprintf/Join-basierte SQL-Klauselbildung). - migrations/0001_mail_documents.sql: statisches Schema, per go:embed eingebettet, über /sql?mode=raw angelegt (kein String-Zusammenbau). - client.go: Index/Search über die strukturierte Manticore-HTTP-JSON-API, Tenant-Filter über strukturiertes equals-Feld statt WHERE-Interpolation. Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-01-PRUEFPROTOKOLL.md): 1. TestNoDynamicSQLClauseBuilding: automatisierter Quelltext-Scan bestätigt keine Sprintf/Join-SQL-Klauselbildung. 2. TestSearch_MaliciousInputDoesNotAlterFieldNames: Injection-artige Eingaben verändern nachweislich keine Feldnamen im gesendeten Payload. 3. TestSearch_FindsExpectedDocument: Funktionstest gegen echtes Manticore auf 192.168.1.131 liefert erwartete Treffer. Zusätzlich TestSearch_TenantIsolation für Akzeptanzkriterium 3. Kein Umbau: storage/crypto/encstorage/dedup unverändert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
704b64fe27
commit
c3bf8100b1
@@ -0,0 +1,98 @@
|
||||
// Integrationstest (SRC-01): echte Manticore-Instanz auf dem Testhost.
|
||||
// Folgt derselben TEST_*-Env-Konvention wie mail/internal/example und
|
||||
// mail/internal/dedup — TEST_MANTICORE_URL.
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setupClient(t *testing.T) *Client {
|
||||
t.Helper()
|
||||
baseURL := os.Getenv("TEST_MANTICORE_URL")
|
||||
if baseURL == "" {
|
||||
t.Skip("TEST_MANTICORE_URL nicht gesetzt, Integrationstest übersprungen")
|
||||
}
|
||||
client := NewClient(baseURL)
|
||||
if err := client.EnsureSchema(context.Background()); err != nil {
|
||||
t.Fatalf("schema: %v", err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// TestSearch_FindsExpectedDocument ist die geforderte Pflichtprüfung 3:
|
||||
// Funktionstest bestätigt, dass die Volltextsuche erwartete Treffer für
|
||||
// einen Testkorpus liefert.
|
||||
func TestSearch_FindsExpectedDocument(t *testing.T) {
|
||||
client := setupClient(t)
|
||||
ctx := context.Background()
|
||||
tenant := "mandant-src01-funktionstest"
|
||||
|
||||
if err := client.Index(ctx, Document{
|
||||
ID: 910001,
|
||||
TenantSlug: tenant,
|
||||
MessageID: "msg-funktionstest-1",
|
||||
Subject: "Quartalsbericht Q3",
|
||||
Body: "Anbei der vollständige Quartalsbericht mit Umsatzzahlen.",
|
||||
}); err != nil {
|
||||
t.Fatalf("index: %v", err)
|
||||
}
|
||||
if err := client.Index(ctx, Document{
|
||||
ID: 910002,
|
||||
TenantSlug: tenant,
|
||||
MessageID: "msg-funktionstest-2",
|
||||
Subject: "Mittagessen morgen",
|
||||
Body: "Wollen wir zusammen essen gehen?",
|
||||
}); err != nil {
|
||||
t.Fatalf("index: %v", err)
|
||||
}
|
||||
|
||||
results, err := client.Search(ctx, tenant, "Quartalsbericht")
|
||||
if err != nil {
|
||||
t.Fatalf("search: %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, r := range results {
|
||||
if r.MessageID == "msg-funktionstest-1" {
|
||||
found = true
|
||||
}
|
||||
if r.MessageID == "msg-funktionstest-2" {
|
||||
t.Fatal("unerwarteter treffer für nicht passende nachricht")
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("erwarteten treffer msg-funktionstest-1 nicht gefunden, habe: %+v", results)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSearch_TenantIsolation ist Akzeptanzkriterium 3 (mandantengetrennt
|
||||
// abfragbar): identischer Inhalt bei zwei Mandanten, Suche bei Mandant A
|
||||
// darf keinen Treffer bei Mandant B liefern.
|
||||
func TestSearch_TenantIsolation(t *testing.T) {
|
||||
client := setupClient(t)
|
||||
ctx := context.Background()
|
||||
tenantA := "mandant-src01-iso-a"
|
||||
tenantB := "mandant-src01-iso-b"
|
||||
|
||||
if err := client.Index(ctx, Document{
|
||||
ID: 910101,
|
||||
TenantSlug: tenantA,
|
||||
MessageID: "msg-iso-a",
|
||||
Subject: "Vertraulicher Betreff Alpha",
|
||||
Body: "Inhalt nur für Mandant A.",
|
||||
}); err != nil {
|
||||
t.Fatalf("index mandant a: %v", err)
|
||||
}
|
||||
|
||||
results, err := client.Search(ctx, tenantB, "Vertraulicher")
|
||||
if err != nil {
|
||||
t.Fatalf("search mandant b: %v", err)
|
||||
}
|
||||
for _, r := range results {
|
||||
if r.MessageID == "msg-iso-a" {
|
||||
t.Fatal("mandant b hat treffer aus mandant a gesehen — mandantentrennung verletzt")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user