Files
felhom.eu/hub/internal/web/agent_only_vouch_test.go
T

102 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package web
import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// Agent-only vouch (`09` §3 decision 250 items 3–4, hub v0.158.0). The golden is baked weekly while the floor carries
// controller releases, so mid-week the vouched golden is behind the fleet. The R-120 gate guards a golden SELECTION:
// an unchanged golden (same version, same sha) is not a selection and must not block an agent-only save; a changed
// golden is judged exactly as before. Both tests drive handleSetArtifacts — the only UI writer of the manifest.
func postArtifacts(t *testing.T, s *Server, form url.Values) *httptest.ResponseRecorder {
t.Helper()
r := httptest.NewRequest(http.MethodPost, "/configuration/artifacts", strings.NewReader(form.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
s.handleSetArtifacts(w, r)
return w
}
// The mid-week situation: golden 0.330.0 vouched on Sunday, the floor has since carried the fleet to 0.335.0, and the
// operator vouches a new agent. RED-PROOF (REPORT, 2026-10-17 night): with the `!goldenUnchanged` condition removed
// from the gate, this fails: `agent-only vouch refused: Location="/configuration?flash=golden_behind_fleet"`.
func TestVouchAgentOnlyWithStaleGoldenIsSaved(t *testing.T) {
s, st := newTestServer(t)
seedReport(t, st, "demo", "0.335.0")
golden := strings.Repeat("b", 64)
if err := st.SetArtifactManifest(store.ArtifactManifest{
AgentVersion: "0.159.0", AgentSHA256: strings.Repeat("a", 64),
GoldenVersion: "0.330.0", GoldenSHA256: golden, MinAgent: "0.150.0",
}); err != nil {
t.Fatal(err)
}
w := postArtifacts(t, s, url.Values{
"agent_version": {"0.160.0"}, "agent_sha256": {strings.Repeat("c", 64)},
"golden_version": {"0.330.0"}, "golden_sha256": {golden}, "min_agent": {"0.150.0"},
})
if loc := w.Header().Get("Location"); !strings.Contains(loc, "flash=artifacts_set") {
t.Fatalf("agent-only vouch refused: Location=%q, want flash=artifacts_set", loc)
}
m := st.GetArtifactManifest()
if m.AgentVersion != "0.160.0" || m.AgentSHA256 != strings.Repeat("c", 64) {
t.Fatalf("agent not saved: agent=%s sha=%s…", m.AgentVersion, m.AgentSHA256[:4])
}
if m.GoldenVersion != "0.330.0" || m.GoldenSHA256 != golden || m.MinAgent != "0.150.0" {
t.Fatalf("golden/MinAgent moved on an agent-only save: %+v", m)
}
}
// A golden CHANGED to an older one is still refused, even though the agent changes in the same save — and nothing is
// written. Also the same version under a different sha (a re-push) is a changed golden.
func TestVouchGoldenChangedToOlderIsStillRefused(t *testing.T) {
s, st := newTestServer(t)
seedReport(t, st, "demo", "0.335.0")
cur := store.ArtifactManifest{
AgentVersion: "0.159.0", AgentSHA256: strings.Repeat("a", 64),
GoldenVersion: "0.330.0", GoldenSHA256: strings.Repeat("b", 64),
}
if err := st.SetArtifactManifest(cur); err != nil {
t.Fatal(err)
}
for name, form := range map[string]url.Values{
"older version": {"agent_version": {"0.160.0"}, "agent_sha256": {strings.Repeat("c", 64)},
"golden_version": {"0.329.0"}, "golden_sha256": {strings.Repeat("d", 64)}},
"same version, other sha": {"agent_version": {"0.160.0"}, "agent_sha256": {strings.Repeat("c", 64)},
"golden_version": {"0.330.0"}, "golden_sha256": {strings.Repeat("e", 64)}},
} {
w := postArtifacts(t, s, form)
if loc := w.Header().Get("Location"); !strings.Contains(loc, "golden_behind_fleet") {
t.Errorf("%s: Location=%q, want the golden_behind_fleet refusal", name, loc)
}
if m := st.GetArtifactManifest(); m != cur {
t.Errorf("%s: refused but the manifest changed: %+v", name, m)
}
}
}
// R-1346: a vouch save names the time of each leg in one line, so the next slow save says where its time went.
// RED-PROOF: drop the timing line → FAILS.
func TestVouchSaveLogsItsTiming(t *testing.T) {
s, st := newTestServer(t)
var logs bytes.Buffer
s.logger = log.New(&logs, "", 0)
seedReport(t, st, "demo", "0.335.0")
w := postArtifacts(t, s, url.Values{"agent_version": {"0.160.0"}, "agent_sha256": {strings.Repeat("c", 64)}})
if loc := w.Header().Get("Location"); !strings.Contains(loc, "flash=artifacts_set") {
t.Fatalf("vouch refused: %q", loc)
}
if !regexp.MustCompile(`artifact vouch timing \(R-1346\): registry probes \d+ms, sha lookups \d+ms, bundle sha \d+ms, fleet check \+ save \d+ms, total \d+ms`).MatchString(logs.String()) {
t.Fatalf("no timing line:\n%s", logs.String())
}
}