package api import ( "encoding/json" "net/http" "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // Part D over the wire: the report ACK WITHHOLDS the controller floor for a box whose agent is below // the golden's MinAgent, and serves it once the agent qualifies. Companion red-proof: revert the // report handler to the ungated EffectiveMinControllerVersion → the held case leaks the floor. func TestReportACK_ManagedFloorHold(t *testing.T) { h, st, _ := newTestHandler(t) st.SetDefaultMinControllerVersion("0.113.0") // controller floor _ = st.SetArtifactManifest(store.ArtifactManifest{GoldenVersion: "0.113.0", MinAgent: "0.81.0"}) // coupled golden if err := st.SaveCustomerConfig(&store.CustomerConfig{CustomerID: "c", RetrievalPassword: "pw", APIKey: "k", ConfigJSON: "{}"}); err != nil { t.Fatal(err) } if err := st.UpsertHost(&store.Host{HostID: "hc", CustomerID: "c", APIKey: "hc-key"}); err != nil { t.Fatal(err) } ackFloor := func() (interface{}, bool) { rr := do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"c"}`) if rr.Code != http.StatusOK { t.Fatalf("report status = %d", rr.Code) } var ack map[string]interface{} _ = json.Unmarshal(rr.Body.Bytes(), &ack) v, ok := ack["min_controller_version"] return v, ok } // Agent below MinAgent → the floor is HELD (ACK omits it). if err := st.SaveHostReport("hc", "c", []byte(`{}`), store.HostReportDenorm{AgentVersion: "0.79.0"}); err != nil { t.Fatal(err) } if v, ok := ackFloor(); ok { t.Errorf("held box: ACK must OMIT the floor, got %v", v) } // Agent updates to ≥ MinAgent → the floor is now served. if err := st.SaveHostReport("hc", "c", []byte(`{}`), store.HostReportDenorm{AgentVersion: "0.82.0"}); err != nil { t.Fatal(err) } if v, ok := ackFloor(); !ok || v != "0.113.0" { t.Errorf("qualified box: ACK must serve the floor 0.113.0, got %v (present=%v)", v, ok) } }