package api import ( "net/http" "strings" "testing" "time" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) func tunnelReport(status, detail string) string { return strings.Replace(validReportBody("h1"), `"cloudflared":{"status":"active"}`, `"cloudflared":{"status":"`+status+`","detail":"`+detail+`"}`, 1) } // R-841: the tunnel alarm fires when the tunnel is not running in TWO reports in a row; running never alarms; // one bad report never alarms; `unknown` never alarms and breaks a run; recovery is announced once. // Red-proof: make checkTunnel alarm on ONE not_running (drop down(1)) and "a single bad report" fails. func TestTunnelAlarm_Sequences(t *testing.T) { cases := []struct { name string seq []string down int recov int }{ {"running never alarms", []string{"running", "running", "running"}, 0, 0}, {"a single bad report does not alarm", []string{"running", "not_running", "running"}, 0, 0}, {"two in a row alarm once", []string{"running", "not_running", "not_running", "not_running"}, 1, 0}, {"unknown never alarms and breaks the run", []string{"not_running", "unknown", "not_running", "unknown"}, 0, 0}, {"recovery after an alarm", []string{"not_running", "not_running", "running"}, 1, 1}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { h, st, _ := newTestHandler(t) st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", APIKey: "ckey", RetrievalPassword: "p"}) st.UpsertHost(&store.Host{HostID: "h1", CustomerID: "c1", APIKey: "HKEY"}) for _, s := range c.seq { if rr := do(h, http.MethodPost, "/host-report", "HKEY", tunnelReport(s, "exited, exit code 1")); rr.Code != 200 { t.Fatalf("report: %d %s", rr.Code, rr.Body.String()) } } since := time.Now().Add(-time.Hour) d, _ := st.GetEventsByType("c1", eventTunnelDown, since) r, _ := st.GetEventsByType("c1", eventTunnelRecovered, since) if len(d) != c.down || len(r) != c.recov { t.Fatalf("tunnel_down=%d tunnel_recovered=%d, want %d / %d", len(d), len(r), c.down, c.recov) } }) } }