package web import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // R-472 SCENARIO E — the floor forms, through the real handlers. func postGlobalFloor(t *testing.T, s *Server, form url.Values) string { t.Helper() r := httptest.NewRequest(http.MethodPost, "/configuration/global-floor", strings.NewReader(form.Encode())) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() s.handleSetGlobalFloor(w, r) return w.Header().Get("Location") } func TestGlobalFloorForm_RefusesAboveGoldenWithoutMinAgent(t *testing.T) { s, st := newTestServer(t) _ = st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.236.0", MinAgent: "0.129.0"}) _ = st.SetGlobalMinControllerVersion("0.236.0") loc := postGlobalFloor(t, s, url.Values{"min_controller_version": {"0.239.0"}}) if !strings.Contains(loc, "flash=floor_needs_min_agent") { t.Fatalf("a floor above the golden without min_agent must be refused, got %q", loc) } if got := st.GetGlobalMinControllerVersion(); got != "0.236.0" { t.Errorf("a refused form must store NOTHING — floor is now %q", got) } loc = postGlobalFloor(t, s, url.Values{"min_controller_version": {"0.239.0"}, "min_agent": {"0.12x"}}) if !strings.Contains(loc, "flash=floor_min_agent_invalid") || st.GetGlobalMinControllerVersion() != "0.236.0" { t.Fatalf("an unparseable min_agent must be refused with nothing stored, got %q", loc) } loc = postGlobalFloor(t, s, url.Values{"min_controller_version": {"0.239.0"}, "min_agent": {"0.129.0"}}) if !strings.Contains(loc, "flash=floor_set") { t.Fatalf("a declared floor must be saved, got %q", loc) } if st.GetGlobalMinControllerVersion() != "0.239.0" || st.GlobalFloorDeclaredMinAgent() != "0.129.0" { t.Errorf("floor=%q declared=%q", st.GetGlobalMinControllerVersion(), st.GlobalFloorDeclaredMinAgent()) } // Inside the golden no declaration is needed — today's behaviour is unchanged. if loc := postGlobalFloor(t, s, url.Values{"min_controller_version": {"0.230.0"}}); !strings.Contains(loc, "flash=floor_set") { t.Errorf("a floor at/below the golden must save without min_agent, got %q", loc) } } func TestCustomerFloorForm_RefusesAboveGoldenWithoutMinAgent(t *testing.T) { s, st := newTestServer(t) _ = st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.236.0", MinAgent: "0.129.0"}) if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c1", RetrievalPassword: "x", APIKey: "y"}); err != nil { t.Fatal(err) } post := func(form url.Values) string { r := httptest.NewRequest(http.MethodPost, "/customers/c1/floor", strings.NewReader(form.Encode())) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() s.handleSetCustomerFloor(w, r, "c1") return w.Header().Get("Location") } if loc := post(url.Values{"min_controller_version": {"0.239.0"}}); !strings.Contains(loc, "flash=floor_needs_min_agent") { t.Fatalf("got %q", loc) } if cfg, _ := st.GetCustomerConfig("c1"); cfg.MinControllerVersion != "" { t.Errorf("a refused override must store nothing, got %q", cfg.MinControllerVersion) } if loc := post(url.Values{"min_controller_version": {"0.239.0"}, "min_agent": {"0.129.0"}}); !strings.Contains(loc, "flash=floor_set") { t.Fatalf("got %q", loc) } if got := st.CustomerFloorDeclaredMinAgent("c1"); got != "0.129.0" { t.Errorf("declared = %q", got) } } // R-472 — the Hosts badge is a template gate, so each branch gets a render test (hub rules: seam-wiring // covers template gates). Served-by-declaration shows the badge; undeclared above the golden shows // "floor held" and NOT the badge; inside the golden (manifest governs) shows neither. func renderHostsForFloor(t *testing.T, floor, declared string) string { t.Helper() s, st := newTestServer(t) _ = st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.236.0", MinAgent: "0.129.0"}) if err := st.UpsertHost(&store.Host{HostID: "demo-hp-01", CustomerID: "c1", APIKey: "k1"}); err != nil { t.Fatal(err) } if err := st.SaveHostReport("demo-hp-01", "c1", []byte(testReportJSON), store.HostReportDenorm{AgentVersion: "0.130.0"}); err != nil { t.Fatal(err) } if err := st.SetGlobalMinControllerVersion(floor); err != nil { t.Fatal(err) } if err := st.SetGlobalFloorDeclaredMinAgent(floor, declared); err != nil { t.Fatal(err) } rr := httptest.NewRecorder() s.handleHostsList(rr, httptest.NewRequest(http.MethodGet, "/hosts", nil)) if rr.Code != http.StatusOK { t.Fatalf("status = %d", rr.Code) } return rr.Body.String() } func TestHostsBadge_EachFloorBranchRenders(t *testing.T) { const badge, held = "floor: declared MinAgent", "floor held" if body := renderHostsForFloor(t, "0.239.0", "0.129.0"); !strings.Contains(body, badge) || strings.Contains(body, held) { t.Errorf("declared floor above the golden: want the badge and no hold (badge=%v held=%v)", strings.Contains(body, badge), strings.Contains(body, held)) } if body := renderHostsForFloor(t, "0.239.0", ""); strings.Contains(body, badge) || !strings.Contains(body, held) { t.Errorf("undeclared floor above the golden: want the hold and no badge (badge=%v held=%v)", strings.Contains(body, badge), strings.Contains(body, held)) } if body := renderHostsForFloor(t, "0.236.0", "0.129.0"); strings.Contains(body, badge) || strings.Contains(body, held) { t.Errorf("floor at the golden: the manifest governs, want neither (badge=%v held=%v)", strings.Contains(body, badge), strings.Contains(body, held)) } } // The customer page renders the declared value back into its min_agent input — the form is reachable // and round-trips what was stored. (Its absence once truncated the whole page render.) func TestCustomerPage_RendersDeclaredMinAgentInput(t *testing.T) { s, st := newTestServer(t) _ = st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.236.0", MinAgent: "0.129.0"}) if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "acme", CustomerName: "Acme", Domain: "acme.hu", RetrievalPassword: "pw", APIKey: "k", Status: "active"}); err != nil { t.Fatal(err) } // The Controller Update card (and its floor form) renders only for a reporting customer. if err := st.SaveReport("acme", []byte(tabsTestReportJSON)); err != nil { t.Fatal(err) } if err := st.SetMinControllerVersion("acme", "0.239.0"); err != nil { t.Fatal(err) } if err := st.SetCustomerFloorDeclaredMinAgent("acme", "0.239.0", "0.129.0"); err != nil { t.Fatal(err) } html := renderCustomerPage(t, s, "acme") if !strings.Contains(html, `name="min_agent" value="0.129.0"`) { t.Error("the per-customer floor form must render min_agent with the declared value") } }