CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) 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) (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
Supply-Chain-Scan (Go) / govulncheck (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-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
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Connect-seitige Webhook-Abonnements ueber Core API-07 (zentrale Webhook-Registry): connect/internal/webhookclient als schlanker HTTP-Client gegen die Registry (Subscribe/Unsubscribe), connect/internal/ webhooksub mit lokaler, verschluesselter Abonnementverwaltung und HTTP-Zustellempfaenger inkl. HMAC-Signaturpruefung. Core API-07 exponiert bislang keinen HTTP-Wrapper fuer physisch getrennte Module (anders als z.B. AUD-08) -- Tests laufen deshalb gegen einen den dokumentierten Core-Vertrag nachbildenden Stub, echte Postgres-Testdatenbank, echte HTTP-Verbindungen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
110 lines
3.7 KiB
Go
110 lines
3.7 KiB
Go
package webhookclient
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// newFakeCoreWebhookAPI steht fuer Core API-07s echten Vertrag (POST
|
|
// /webhooks/subscriptions, DELETE /webhooks/subscriptions/{id}) -- testet
|
|
// die echte HTTP-Kette (Client -> echter Server -> Antwort-Dekodierung),
|
|
// nicht nur eine wirkungslose lokale Struktur.
|
|
func newFakeCoreWebhookAPI(t *testing.T) (*httptest.Server, *[]string) {
|
|
t.Helper()
|
|
var calls []string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls = append(calls, r.Method+" "+r.URL.String())
|
|
if r.Header.Get("X-Service-Token") == "" {
|
|
t.Fatalf("Service-Token fehlt bei %s %s", r.Method, r.URL.Path)
|
|
}
|
|
switch {
|
|
case r.Method == http.MethodPost && r.URL.Path == "/webhooks/subscriptions":
|
|
var in subscribeRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
t.Fatalf("body dekodieren: %v", err)
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
_ = json.NewEncoder(w).Encode(Subscription{
|
|
ID: "core-sub-1", EventType: in.EventType, TargetURL: in.TargetURL, CreatedAt: time.Now().UTC(),
|
|
})
|
|
case r.Method == http.MethodDelete:
|
|
w.WriteHeader(http.StatusNoContent)
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
return srv, &calls
|
|
}
|
|
|
|
func TestSubscribe_SendsRequestFields_ReturnsSubscription(t *testing.T) {
|
|
srv, _ := newFakeCoreWebhookAPI(t)
|
|
defer srv.Close()
|
|
|
|
c := New(srv.URL, "svc-token-123")
|
|
sub, err := c.Subscribe(context.Background(), "acme", "dms.document.archived", "https://connect.example/d/abc", "shh-secret")
|
|
if err != nil {
|
|
t.Fatalf("Subscribe: %v", err)
|
|
}
|
|
if sub.ID != "core-sub-1" {
|
|
t.Fatalf("erwartete core-vergebene id, bekam %q", sub.ID)
|
|
}
|
|
if sub.EventType != "dms.document.archived" {
|
|
t.Fatalf("event_type nicht wie gesendet: %q", sub.EventType)
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_UnexpectedStatus_ReturnsError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := New(srv.URL, "svc-token-123")
|
|
if _, err := c.Subscribe(context.Background(), "acme", "dms.document.archived", "https://connect.example/d/abc", "shh"); err == nil {
|
|
t.Fatal("erwarteter Fehler bei Status 500 blieb aus")
|
|
}
|
|
}
|
|
|
|
func TestSubscribe_TransportError_ReturnsError(t *testing.T) {
|
|
c := New("http://127.0.0.1:1", "svc-token-123")
|
|
if _, err := c.Subscribe(context.Background(), "acme", "dms.document.archived", "https://connect.example/d/abc", "shh"); err == nil {
|
|
t.Fatal("erwarteter Transport-Fehler blieb aus")
|
|
}
|
|
}
|
|
|
|
func TestUnsubscribe_SendsServiceTokenAndTenant_Succeeds(t *testing.T) {
|
|
srv, calls := newFakeCoreWebhookAPI(t)
|
|
defer srv.Close()
|
|
|
|
c := New(srv.URL, "svc-token-123")
|
|
if err := c.Unsubscribe(context.Background(), "acme", "core-sub-1"); err != nil {
|
|
t.Fatalf("Unsubscribe: %v", err)
|
|
}
|
|
if len(*calls) != 1 || (*calls)[0] != "DELETE /webhooks/subscriptions/core-sub-1?tenant_slug=acme" {
|
|
t.Fatalf("unerwartete aufrufe: %v", *calls)
|
|
}
|
|
}
|
|
|
|
func TestUnsubscribe_UnexpectedStatus_ReturnsError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := New(srv.URL, "svc-token-123")
|
|
if err := c.Unsubscribe(context.Background(), "acme", "core-sub-1"); err == nil {
|
|
t.Fatal("erwarteter Fehler bei Status 500 blieb aus")
|
|
}
|
|
}
|
|
|
|
func TestUnsubscribe_TransportError_ReturnsError(t *testing.T) {
|
|
c := New("http://127.0.0.1:1", "svc-token-123")
|
|
if err := c.Unsubscribe(context.Background(), "acme", "core-sub-1"); err == nil {
|
|
t.Fatal("erwarteter Transport-Fehler blieb aus")
|
|
}
|
|
}
|