CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Ergaenzt die bestehende Testdecke (APR-01/04, TSK-03/05, CFG-02/04/05) um zwei End-zu-Ende-/Race-Tests gegen echte Postgres-Testdatenbanken: eine Parallelentscheidung zweier Vertreter auf derselben delegierten Freigabe, und ein Abgleich des echten Zustellprotokolls (notification_jobs) ueber den realen CFG-05-HTTP-Pfad statt eines Mock-Dispatchers. Keine Produktionslogik musste korrigiert werden; Befunde und Testlaeufe sind in docs/QA-07-PRUEFPROTOKOLL.md dokumentiert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
package notifyapi
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// QA-07 Pruefung 3: Zustellprotokoll (notification_jobs, Core CFG-02) wird
|
|
// gegen die aus den simulierten Workflow-Prozesspfaden (Ablehnung, Eskalation,
|
|
// Fristablauf) erwarteten Ereignisse abgeglichen -- jedes erwartete Ereignis
|
|
// erscheint genau einmal, kein Duplikat, keine Luecke. Treibt denselben
|
|
// HTTP-Pfad an, den workflow/internal/escalationnotify.DispatchDue und
|
|
// Aehnliches live gegen Core CFG-05 nutzen.
|
|
func TestE2E_DeliveryLogMatchesExpectedWorkflowEvents(t *testing.T) {
|
|
prefs, dispatcher, pool := setupTest(t)
|
|
ctx := context.Background()
|
|
|
|
mux := http.NewServeMux()
|
|
Mount(mux, prefs, dispatcher, testToken)
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
tenant := "acme"
|
|
if err := prefs.Set(ctx, tenant, "cfg05_qa07_muted", "workflow.task.escalated", "email", false); err != nil {
|
|
t.Fatalf("praeferenz deaktivieren: %v", err)
|
|
}
|
|
|
|
type expectedEvent struct {
|
|
userID string
|
|
eventType string
|
|
recipient string
|
|
}
|
|
events := []expectedEvent{
|
|
{userID: "cfg05_qa07_alice", eventType: "workflow.approval.rejected", recipient: "cfg05_qa07_alice@acme.example"},
|
|
{userID: "cfg05_qa07_bob", eventType: "workflow.approval.delegated", recipient: "cfg05_qa07_bob@acme.example"},
|
|
{userID: "cfg05_qa07_carol", eventType: "workflow.task.escalated", recipient: "cfg05_qa07_carol@acme.example"},
|
|
{userID: "cfg05_qa07_dave", eventType: "workflow.task.reminder_due", recipient: "cfg05_qa07_dave@acme.example"},
|
|
{userID: "cfg05_qa07_muted", eventType: "workflow.task.escalated", recipient: "cfg05_qa07_muted@acme.example"},
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for _, ev := range events {
|
|
wg.Add(1)
|
|
go func(ev expectedEvent) {
|
|
defer wg.Done()
|
|
resp := post(t, server, testToken, enqueueRequest{
|
|
TenantSlug: tenant, UserID: ev.userID, EventType: ev.eventType,
|
|
Channel: "email", Recipient: ev.recipient,
|
|
Payload: map[string]any{"task_id": "qa07-" + ev.userID},
|
|
})
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("enqueue fuer %s/%s: status = %d", ev.userID, ev.eventType, resp.StatusCode)
|
|
}
|
|
}(ev)
|
|
}
|
|
wg.Wait()
|
|
|
|
rows, err := pool.Query(ctx, `
|
|
SELECT recipient, payload->>'task_id'
|
|
FROM notification_jobs
|
|
WHERE recipient LIKE 'cfg05\_qa07\_%' ESCAPE '\'
|
|
`)
|
|
if err != nil {
|
|
t.Fatalf("zustellprotokoll lesen: %v", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
seen := map[string]int{}
|
|
for rows.Next() {
|
|
var recipient, taskID string
|
|
if err := rows.Scan(&recipient, &taskID); err != nil {
|
|
t.Fatalf("zeile lesen: %v", err)
|
|
}
|
|
seen[recipient]++
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
t.Fatalf("iterieren: %v", err)
|
|
}
|
|
|
|
for _, ev := range events {
|
|
if ev.userID == "cfg05_qa07_muted" {
|
|
continue
|
|
}
|
|
if seen[ev.recipient] != 1 {
|
|
t.Fatalf("empfaenger %s: %d eintraege im zustellprotokoll, want genau 1 (kein duplikat, keine luecke)", ev.recipient, seen[ev.recipient])
|
|
}
|
|
}
|
|
if n := seen["cfg05_qa07_muted@acme.example"]; n != 0 {
|
|
t.Fatalf("deaktivierter kanal hat trotzdem %d eintraege im zustellprotokoll erzeugt, want 0", n)
|
|
}
|
|
}
|