19dbb02ee3
Adds disk_health_degraded to allowedEventTypes so the controller's per-disk SMART degradation notification is ingested, not 400-rejected. Deliberately no customerMessages entry (like offbox_enlarge_blocked) — the controller's dynamic Hungarian message (disk label + attributes) is preserved by the templates.go fallback. Test + red-proof.
131 lines
4.8 KiB
Go
131 lines
4.8 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
func eventBody(severity, eventType string) string {
|
|
return `{"customer_id":"c1","event_type":"` + eventType + `","severity":"` + severity + `","message":"probe"}`
|
|
}
|
|
|
|
func newEventTestHandler(t *testing.T) (*Handler, *store.Store) {
|
|
t.Helper()
|
|
h, st, _ := newTestHandler(t)
|
|
if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", APIKey: "ckey", RetrievalPassword: "p"}); err != nil {
|
|
t.Fatalf("SaveCustomerConfig: %v", err)
|
|
}
|
|
return h, st
|
|
}
|
|
|
|
// Scenario A: a controller-POSTed "critical" event must be stored as "critical",
|
|
// not coerced to "info" (pre-v0.31.0 bug: the severity switch omitted "critical",
|
|
// so critical events silently became info and never notified).
|
|
func TestHandleEvent_CriticalPreserved(t *testing.T) {
|
|
h, st := newEventTestHandler(t)
|
|
|
|
rr := do(h, http.MethodPost, "/event", "ckey", eventBody("critical", "test"))
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
evs, err := st.GetRecentEvents("c1", 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentEvents: %v", err)
|
|
}
|
|
if len(evs) != 1 {
|
|
t.Fatalf("stored events = %d, want 1", len(evs))
|
|
}
|
|
if evs[0].Severity != "critical" {
|
|
t.Errorf("stored severity = %q, want %q (critical must survive ingest)", evs[0].Severity, "critical")
|
|
}
|
|
}
|
|
|
|
// Scenario B: unknown severities still coerce to "info" — the coercion contract is
|
|
// exact-match lowercase; only "critical" joined the known set.
|
|
func TestHandleEvent_UnknownSeverityCoercesToInfo(t *testing.T) {
|
|
h, st := newEventTestHandler(t)
|
|
|
|
rr := do(h, http.MethodPost, "/event", "ckey", eventBody("banana", "test"))
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
evs, err := st.GetRecentEvents("c1", 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentEvents: %v", err)
|
|
}
|
|
if len(evs) != 1 || evs[0].Severity != "info" {
|
|
t.Errorf("stored = %+v, want exactly one event with severity info", evs)
|
|
}
|
|
}
|
|
|
|
// Scenario C: an event_type outside allowedEventTypes is rejected with 400 and
|
|
// nothing is stored (locks the allowlist behavior).
|
|
func TestHandleEvent_UnknownEventTypeRejected(t *testing.T) {
|
|
h, st := newEventTestHandler(t)
|
|
|
|
rr := do(h, http.MethodPost, "/event", "ckey", eventBody("error", "not-a-real-type"))
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d, want 400; body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
evs, err := st.GetRecentEvents("c1", 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentEvents: %v", err)
|
|
}
|
|
if len(evs) != 0 {
|
|
t.Errorf("stored events = %d, want 0 after a rejected event_type", len(evs))
|
|
}
|
|
}
|
|
|
|
// Scenario D (controller v0.134.1): offbox_enlarge_blocked is an accepted controller-pushed type —
|
|
// the enlarge-blocked notification's delivery chain starts at hub ingestion. Red-proof: removing the
|
|
// allowlist entry makes this 400 (the pre-fix behavior that dropped the customer email).
|
|
func TestHandleEvent_OffboxEnlargeBlockedAccepted(t *testing.T) {
|
|
h, st := newEventTestHandler(t)
|
|
|
|
rr := do(h, http.MethodPost, "/event", "ckey", eventBody("warning", "offbox_enlarge_blocked"))
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (offbox_enlarge_blocked must be allowlisted); body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
evs, err := st.GetRecentEvents("c1", 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentEvents: %v", err)
|
|
}
|
|
if len(evs) != 1 || evs[0].EventType != "offbox_enlarge_blocked" {
|
|
t.Errorf("stored = %+v, want exactly one offbox_enlarge_blocked event", evs)
|
|
}
|
|
}
|
|
|
|
// Scenario (controller v0.169.0): disk_health_degraded is an accepted controller-pushed type — the
|
|
// disk-health degradation email's delivery chain starts at hub ingestion. Red-proof: removing the
|
|
// allowlist entry makes this 400 (which would silently drop the customer's disk-degradation email).
|
|
func TestHandleEvent_DiskHealthDegradedAccepted(t *testing.T) {
|
|
h, st := newEventTestHandler(t)
|
|
|
|
rr := do(h, http.MethodPost, "/event", "ckey", eventBody("warning", "disk_health_degraded"))
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200 (disk_health_degraded must be allowlisted); body=%s", rr.Code, rr.Body.String())
|
|
}
|
|
evs, err := st.GetRecentEvents("c1", 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRecentEvents: %v", err)
|
|
}
|
|
if len(evs) != 1 || evs[0].EventType != "disk_health_degraded" {
|
|
t.Errorf("stored = %+v, want exactly one disk_health_degraded event", evs)
|
|
}
|
|
}
|
|
|
|
// Auth: no bearer at all is a 401 and stores nothing.
|
|
func TestHandleEvent_Unauthorized(t *testing.T) {
|
|
h, st := newEventTestHandler(t)
|
|
|
|
rr := do(h, http.MethodPost, "/event", "", eventBody("critical", "test"))
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Fatalf("status = %d, want 401", rr.Code)
|
|
}
|
|
if evs, _ := st.GetRecentEvents("c1", 10); len(evs) != 0 {
|
|
t.Errorf("stored events = %d, want 0 without auth", len(evs))
|
|
}
|
|
}
|