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)) } } // 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)) } }