9c94a9c6c0
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
74 lines
3.1 KiB
Go
74 lines
3.1 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-366 slice 2 (`09` §3 decision 168): archives the box's restore-test skipped as written with ANOTHER key reach the
|
|
// operator as ONE line per change of the set — not per archive, not per report; never mailed (info), never the
|
|
// household's.
|
|
|
|
func reportWithForeign(foreign string) string {
|
|
body := validReportBody("h1")
|
|
return strings.TrimSuffix(body, "}") + `,"foreign_key_archives":{"tiers":` + foreign + `}}`
|
|
}
|
|
|
|
func foreignEvents(t *testing.T, st *store.Store) []store.Event {
|
|
t.Helper()
|
|
ev, err := st.GetEventsByType("c1", eventForeignKeyArchives, time.Now().Add(-time.Hour))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ev
|
|
}
|
|
|
|
// COMPANION RED-PROOF (observed): remove the noteForeignKeyArchives call from handleHostReport → this fails with
|
|
// "a new set of other-key archives must record exactly one operator event; got 0". Restored.
|
|
func TestR366_ForeignKeyArchivesOneEventPerChange(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"})
|
|
set := `[{"target":"felhom-pbs","count":2,"oldest":"2026-08-18T03:58:43Z","newest":"2026-08-19T03:58:43Z"}]`
|
|
|
|
for i := 0; i < 3; i++ { // the same set on three reports → one event
|
|
if rr := do(h, http.MethodPost, "/host-report", "HKEY", reportWithForeign(set)); rr.Code != http.StatusOK {
|
|
t.Fatalf("report %d: %d %s", i, rr.Code, rr.Body.String())
|
|
}
|
|
}
|
|
ev := foreignEvents(t, st)
|
|
if len(ev) != 1 {
|
|
t.Fatalf("a new set of other-key archives must record exactly one operator event; got %d", len(ev))
|
|
}
|
|
if ev[0].Severity != "info" || !strings.Contains(ev[0].Message, "2 whole-guest backup archive(s)") ||
|
|
!strings.Contains(ev[0].Message, "felhom-pbs (2026-08-18 … 2026-08-19)") {
|
|
t.Fatalf("the line must name the count, the tier and the date range; got %s %q", ev[0].Severity, ev[0].Message)
|
|
}
|
|
|
|
// An older agent or a fresh restart (the stanza absent) keeps the state: no new event.
|
|
do(h, http.MethodPost, "/host-report", "HKEY", validReportBody("h1"))
|
|
if n := len(foreignEvents(t, st)); n != 1 {
|
|
t.Fatalf("an absent stanza must keep the state; got %d events", n)
|
|
}
|
|
|
|
// The set changes (a third archive) → one more line.
|
|
do(h, http.MethodPost, "/host-report", "HKEY", reportWithForeign(`[{"target":"felhom-pbs","count":3,"oldest":"2026-08-18T03:58:43Z","newest":"2026-08-25T03:58:43Z"}]`))
|
|
if n := len(foreignEvents(t, st)); n != 2 {
|
|
t.Fatalf("a changed set must record one more line; got %d events", n)
|
|
}
|
|
|
|
// Evaluated, none left → the state clears silently; the same set coming back later is news again.
|
|
do(h, http.MethodPost, "/host-report", "HKEY", reportWithForeign(`[]`))
|
|
if n := len(foreignEvents(t, st)); n != 2 {
|
|
t.Fatalf("an empty list clears without an event; got %d", n)
|
|
}
|
|
do(h, http.MethodPost, "/host-report", "HKEY", reportWithForeign(set))
|
|
if n := len(foreignEvents(t, st)); n != 3 {
|
|
t.Fatalf("after a clear, a set is news again; got %d", n)
|
|
}
|
|
}
|