package web import ( "net/http" "net/http/httptest" "net/url" "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) } } }