88b0a2e761
gates / gates (push) Successful in 30s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
52 lines
2.0 KiB
Go
52 lines
2.0 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|