a6acb0f762
gates / gates (push) Successful in 6m27s
Security review of 50e47e29: requested_at is the box's clock — a future value is cut to the hub's
now; CustomerDeliberatelyOff now needs every host of the customer held (one machine of two no
longer silences the other), and fails closed on a read error or no host. Both red-proofed.
61 lines
2.6 KiB
Go
61 lines
2.6 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-998: the agent's deliberate_off field sets the host row's hold; the next report without it (a new boot — the box
|
|
// was switched back on) clears it. Each change leaves one info event on the customer's list; a repeat leaves none.
|
|
func TestHostReport_DeliberateOffSetThenCleared(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"})
|
|
when := time.Now().UTC().Add(-time.Hour).Truncate(time.Second)
|
|
withOff := strings.TrimSuffix(validReportBody("h1"), "}") + `,"deliberate_off":{"requested_at":"` + when.Format(time.RFC3339) + `"}}`
|
|
|
|
for i := 0; i < 2; i++ { // the field rides every report until the box is down; the second must not re-record
|
|
if rr := do(h, http.MethodPost, "/host-report", "HKEY", withOff); rr.Code != 200 {
|
|
t.Fatalf("report %d: %d %s", i, rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
at := st.HostDeliberateOffAt("h1")
|
|
if at == nil || !at.Equal(when) {
|
|
t.Fatalf("hold = %v, want %v", at, when)
|
|
}
|
|
evs, _ := st.GetEventsByType("c1", eventHostDeliberateOff, time.Now().Add(-time.Hour))
|
|
if len(evs) != 1 || evs[0].Severity != "info" {
|
|
t.Fatalf("set events = %+v, want exactly one info event", evs)
|
|
}
|
|
|
|
if rr := do(h, http.MethodPost, "/host-report", "HKEY", validReportBody("h1")); rr.Code != 200 {
|
|
t.Fatalf("plain report: %d", rr.Code)
|
|
}
|
|
if st.HostDeliberateOffAt("h1") != nil {
|
|
t.Fatal("a report without the field must clear the hold")
|
|
}
|
|
ended, _ := st.GetEventsByType("c1", eventHostDeliberateOffEnded, time.Now().Add(-time.Hour))
|
|
if len(ended) != 1 {
|
|
t.Fatalf("ended events = %d, want 1", len(ended))
|
|
}
|
|
}
|
|
|
|
// A future requested_at (the box's clock) must not stretch the hold: it is cut to the hub's now.
|
|
func TestHostReport_DeliberateOffFutureTimeClamped(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"})
|
|
body := strings.TrimSuffix(validReportBody("h1"), "}") + `,"deliberate_off":{"requested_at":"2099-01-01T00:00:00Z"}}`
|
|
if rr := do(h, http.MethodPost, "/host-report", "HKEY", body); rr.Code != 200 {
|
|
t.Fatalf("report: %d", rr.Code)
|
|
}
|
|
at := st.HostDeliberateOffAt("h1")
|
|
if at == nil || at.After(time.Now().Add(time.Minute)) {
|
|
t.Fatalf("hold anchored at %v — a future box time must be cut to the hub's now", at)
|
|
}
|
|
}
|