79 lines
3.3 KiB
Go
79 lines
3.3 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-1204: the box's tunnel to ep0 — a future-stamped handshake (the clock ran ahead) or no handshake for 6 h raises
|
|
// wg_tunnel_stale ONCE (edge-triggered from the host's newest tunnel event), and wg_tunnel_ok once when healthy; a
|
|
// healthy tunnel, an inactive one and an older agent are not judged.
|
|
// RED-PROOF: with checkWGTunnel not called from the host-report handler the first count fails (0 events); with
|
|
// wgTunnelStale ignoring handshake_future_s the clock case fails — seen failing 2026-10-22.
|
|
func tunnelReportBody(hostID, wg string) string {
|
|
b := validReportBody(hostID)
|
|
i := strings.LastIndex(b, "}")
|
|
return b[:i] + `,"wireguard":` + wg + b[i:]
|
|
}
|
|
|
|
func TestR1204_TheClockAheadTunnelAlarmsOnceAndClears(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"})
|
|
post := func(wg string) {
|
|
t.Helper()
|
|
if rr := do(h, http.MethodPost, "/host-report", "HKEY", tunnelReportBody("h1", wg)); rr.Code != http.StatusOK {
|
|
t.Fatalf("host-report = %d %s", rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
count := func(typ string) int {
|
|
evs, _ := st.GetEventsByType("c1", typ, time.Time{})
|
|
return len(evs)
|
|
}
|
|
post(`{"registered":true,"active":true,"last_handshake_age_s":60}`) // healthy
|
|
post(`{"registered":true,"active":false}`) // service down: not judged here
|
|
if count(eventWGTunnelStale) != 0 {
|
|
t.Fatal("a healthy or inactive tunnel must not alarm")
|
|
}
|
|
post(`{"registered":true,"active":true,"last_handshake_age_s":0,"handshake_future_s":63072000}`) // ran 2 years ahead
|
|
post(`{"registered":true,"active":true,"last_handshake_age_s":0,"handshake_future_s":63071100}`)
|
|
if n := count(eventWGTunnelStale); n != 1 {
|
|
t.Fatalf("wg_tunnel_stale = %d, want exactly 1 (edge-triggered)", n)
|
|
}
|
|
evs, _ := st.GetEventsByType("c1", eventWGTunnelStale, time.Time{})
|
|
if !strings.Contains(evs[0].Message, "Host h1:") || !strings.Contains(evs[0].Message, "FUTURE") {
|
|
t.Fatalf("the alarm must name the host and the clock cause: %q", evs[0].Message)
|
|
}
|
|
post(`{"registered":true,"active":true,"last_handshake_age_s":45}`)
|
|
post(`{"registered":true,"active":true,"last_handshake_age_s":50}`)
|
|
if count(eventWGTunnelOK) != 1 {
|
|
t.Fatalf("wg_tunnel_ok = %d, want 1", count(eventWGTunnelOK))
|
|
}
|
|
post(`{"registered":true,"active":true,"last_handshake_age_s":30000}`) // 8 h 20 min with no handshake
|
|
if count(eventWGTunnelStale) != 2 {
|
|
t.Fatal("six hours without a handshake while reporting must alarm again")
|
|
}
|
|
}
|
|
|
|
func TestR1204_WhatIsJudged(t *testing.T) {
|
|
i := func(v int64) *int64 { return &v }
|
|
for _, c := range []struct {
|
|
w *hostWireguard
|
|
want bool
|
|
}{
|
|
{nil, false},
|
|
{&hostWireguard{Registered: true, Active: true, LastHandshakeAgeS: i(120)}, false},
|
|
{&hostWireguard{Registered: true, Active: true, LastHandshakeAgeS: i(6*3600 + 1)}, true},
|
|
{&hostWireguard{Registered: true, Active: true, LastHandshakeAgeS: i(0), HandshakeFutureS: i(3600)}, true},
|
|
{&hostWireguard{Registered: false, Active: true, LastHandshakeAgeS: i(99999)}, false},
|
|
} {
|
|
if got, _ := wgTunnelStale(c.w); got != c.want {
|
|
t.Errorf("%+v: stale=%t, want %t", c.w, got, c.want)
|
|
}
|
|
}
|
|
}
|