bbecf0592e
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
96 lines
3.6 KiB
Go
96 lines
3.6 KiB
Go
package store
|
|
|
|
import "testing"
|
|
|
|
// setHostAgent enrolls a host for customerID and records its agent version (the reality column).
|
|
func setHostAgent(t *testing.T, s *Store, customerID, hostID, agentVer string) {
|
|
t.Helper()
|
|
if err := s.UpsertHost(&Host{HostID: hostID, CustomerID: customerID, APIKey: hostID + "-key"}); err != nil {
|
|
t.Fatalf("UpsertHost: %v", err)
|
|
}
|
|
if agentVer != "" {
|
|
if err := s.SaveHostReport(hostID, customerID, []byte(`{}`), HostReportDenorm{AgentVersion: agentVer}); err != nil {
|
|
t.Fatalf("SaveHostReport: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResolveManagedFloor gates the served controller floor on the box's agent meeting the golden's
|
|
// MinAgent (Part D — the hub-enforced "agent BEFORE controller floor" rule).
|
|
func TestResolveManagedFloor(t *testing.T) {
|
|
base := func(t *testing.T) *Store {
|
|
s := newTestStore(t)
|
|
_ = s.SetGlobalMinControllerVersion("0.113.0") // the controller floor
|
|
return s
|
|
}
|
|
|
|
t.Run("uncoupled release (MinAgent empty) → serve the floor, no gating", func(t *testing.T) {
|
|
s := base(t)
|
|
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.113.0", MinAgent: ""})
|
|
setHostAgent(t, s, "c1", "h1", "0.70.0") // ancient agent, but release is uncoupled
|
|
fd := s.ResolveManagedFloor("c1")
|
|
if fd.Held || fd.Floor != "0.113.0" {
|
|
t.Fatalf("uncoupled must serve: %+v", fd)
|
|
}
|
|
})
|
|
|
|
t.Run("agent ≥ MinAgent → serve the floor", func(t *testing.T) {
|
|
s := base(t)
|
|
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.113.0", MinAgent: "0.81.0"})
|
|
setHostAgent(t, s, "c1", "h1", "0.82.0")
|
|
fd := s.ResolveManagedFloor("c1")
|
|
if fd.Held || fd.Floor != "0.113.0" {
|
|
t.Fatalf("at/above MinAgent must serve: %+v", fd)
|
|
}
|
|
})
|
|
|
|
t.Run("agent < MinAgent → HELD, no floor served, flagged", func(t *testing.T) {
|
|
s := base(t)
|
|
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.113.0", MinAgent: "0.81.0"})
|
|
setHostAgent(t, s, "c1", "h1", "0.79.0")
|
|
fd := s.ResolveManagedFloor("c1")
|
|
if !fd.Held || fd.Floor != "" {
|
|
t.Fatalf("below MinAgent must HOLD: %+v", fd)
|
|
}
|
|
if fd.AgentVersion != "0.79.0" || fd.MinAgent != "0.81.0" {
|
|
t.Fatalf("held decision must carry the inputs: %+v", fd)
|
|
}
|
|
})
|
|
|
|
t.Run("agent_version empty (never reported) → HELD (fail-safe)", func(t *testing.T) {
|
|
s := base(t)
|
|
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.113.0", MinAgent: "0.81.0"})
|
|
setHostAgent(t, s, "c1", "h1", "") // enrolled, no report yet
|
|
fd := s.ResolveManagedFloor("c1")
|
|
if !fd.Held || fd.Floor != "" {
|
|
t.Fatalf("unknown agent must HOLD (never push blind): %+v", fd)
|
|
}
|
|
})
|
|
|
|
t.Run("no floor configured → nothing to gate", func(t *testing.T) {
|
|
s := newTestStore(t) // no global floor
|
|
_ = s.SetArtifactManifest(ArtifactManifest{MinAgent: "0.81.0"})
|
|
setHostAgent(t, s, "c1", "h1", "0.70.0")
|
|
fd := s.ResolveManagedFloor("c1")
|
|
if fd.Held || fd.Floor != "" {
|
|
t.Fatalf("no floor → no gate, no hold: %+v", fd)
|
|
}
|
|
})
|
|
|
|
// Fleet discriminator (the §Part-D companion): in ONE fleet, an at-MinAgent box is served while a
|
|
// below-MinAgent box is held. Companion red-proof: drop the hold branch → both get served → the
|
|
// held assertion fails.
|
|
t.Run("fleet: at-MinAgent served, below-MinAgent held", func(t *testing.T) {
|
|
s := base(t)
|
|
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.113.0", MinAgent: "0.81.0"})
|
|
setHostAgent(t, s, "current", "hc", "0.82.0")
|
|
setHostAgent(t, s, "old", "ho", "0.79.0")
|
|
if fd := s.ResolveManagedFloor("current"); fd.Held || fd.Floor != "0.113.0" {
|
|
t.Errorf("current box must be served: %+v", fd)
|
|
}
|
|
if fd := s.ResolveManagedFloor("old"); !fd.Held || fd.Floor != "" {
|
|
t.Errorf("old box must be held: %+v", fd)
|
|
}
|
|
})
|
|
}
|