Files
felhom.eu/hub/internal/api/floor_ack_test.go
T
admin 30380a59f4 hub v0.15.0: Phase 2 managed updates — per-customer controller-version floor
Operator sets a minimum controller version (FLOOR), per-customer defaulting to a
global floor; the report ACK returns the effective floor + latest_version so the
controller auto-updates to the floor when below it (latest stays the opt-in button).

- store: min_controller_version column + hub_settings global floor + Effective/
  Get/SetGlobal/SetMin resolution + config/env DEFAULT_MIN_CONTROLLER_VERSION
- handler: report ACK {min_controller_version, latest_version}; LatestVersionProvider
- web: global floor editor + per-customer override form + Floor column (English)
- tests: floor resolution + ACK + render; override-precedence red-proof verified

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSZmmSFVzGwEzhYmxbkgBK
2026-06-27 11:59:30 +02:00

68 lines
2.3 KiB
Go

package api
import (
"encoding/json"
"net/http"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
type fakeLatest struct{ v string }
func (f fakeLatest) LatestVersion() string { return f.v }
// The controller report ACK advertises the effective floor + latest version (Phase 2).
func TestReportACK_FloorAndLatest(t *testing.T) {
h, st, _ := newTestHandler(t)
h.SetLatestVersionProvider(fakeLatest{v: "0.86.0"})
// Customer with a per-customer override; a global default also set (override must win in the ACK).
st.SetDefaultMinControllerVersion("0.85.0")
if err := st.SaveCustomerConfig(&store.CustomerConfig{
CustomerID: "c", RetrievalPassword: "pw", APIKey: "k", ConfigJSON: "{}",
MinControllerVersion: "0.87.0",
}); err != nil {
t.Fatalf("SaveCustomerConfig: %v", err)
}
rr := do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"c"}`)
if rr.Code != http.StatusOK {
t.Fatalf("report status = %d, want 200 (body=%s)", rr.Code, rr.Body.String())
}
var ack map[string]interface{}
if err := json.Unmarshal(rr.Body.Bytes(), &ack); err != nil {
t.Fatalf("decode ACK: %v", err)
}
if ack["min_controller_version"] != "0.87.0" {
t.Errorf("ACK min_controller_version = %v, want 0.87.0 (override beats global)", ack["min_controller_version"])
}
if ack["latest_version"] != "0.86.0" {
t.Errorf("ACK latest_version = %v, want 0.86.0", ack["latest_version"])
}
}
// No floor configured anywhere → the ACK omits min_controller_version (Phase 2 inert).
func TestReportACK_NoFloorOmitted(t *testing.T) {
h, st, _ := newTestHandler(t)
if err := st.SaveCustomerConfig(&store.CustomerConfig{
CustomerID: "c", RetrievalPassword: "pw", APIKey: "k", ConfigJSON: "{}",
}); err != nil {
t.Fatalf("SaveCustomerConfig: %v", err)
}
rr := do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"c"}`)
if rr.Code != http.StatusOK {
t.Fatalf("report status = %d, want 200", rr.Code)
}
var ack map[string]interface{}
json.Unmarshal(rr.Body.Bytes(), &ack)
if _, ok := ack["min_controller_version"]; ok {
t.Errorf("ACK should omit min_controller_version when no floor; got %v", ack["min_controller_version"])
}
// latest_version is also omitted when no provider wired.
if _, ok := ack["latest_version"]; ok {
t.Errorf("ACK should omit latest_version when no provider; got %v", ack["latest_version"])
}
}