Files
felhom.eu/hub/internal/api/config_version_ack_test.go
T
admin a3ac6c9488 hub v0.26.0: pull-based config delivery + retire inbound GUI controls
config_version counter (bumped on every config save) advertised in the report
ACK; controller re-pulls + self-restarts on a change. Retire Trigger Update /
Push Config / Pull Config / Show Diff handlers+routes+buttons and the inbound
geo-notify (keep hub->Cloudflare geo removal). Setup command -> host-install;
delete dead customer.html + config_detail.html. Closes AUDIT-hub-gui F-S1/F-S4.

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

72 lines
2.6 KiB
Go

package api
import (
"encoding/json"
"net/http"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
)
// The report ACK advertises the per-customer config_version (v0.26.0). The controller compares it
// against its last-applied version and re-pulls + self-restarts on a change.
func TestReportACK_ConfigVersion(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)
}
// First report: ACK carries the baseline version (1).
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)
}
cv, ok := ack["config_version"]
if !ok {
t.Fatalf("ACK missing config_version for a config-managed customer")
}
if cv.(float64) != 1 {
t.Errorf("ACK config_version = %v, want 1 (baseline)", cv)
}
// Edit the config → the version bumps → the next ACK carries the new version. This is what makes
// the box re-pull + restart. RED-PROOF: if SaveCustomerConfig did NOT bump (the bump is dropped),
// the ACK would still report 1 here and the box would never converge — this assertion fails.
if err := st.SaveCustomerConfig(&store.CustomerConfig{
CustomerID: "c", RetrievalPassword: "pw", APIKey: "k", ConfigJSON: `{"git":{"username":"x"}}`,
}); err != nil {
t.Fatalf("SaveCustomerConfig (edit): %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)
}
json.Unmarshal(rr.Body.Bytes(), &ack)
if ack["config_version"].(float64) != 2 {
t.Errorf("ACK config_version after edit = %v, want 2", ack["config_version"])
}
}
// A report-only customer (no config row) gets no config_version field — it has nothing to pull, and
// an old controller that ignores the field behaves exactly as before.
func TestReportACK_ConfigVersionOmittedWhenNoConfig(t *testing.T) {
h, _, _ := newTestHandler(t)
rr := do(h, http.MethodPost, "/report", globalKey, `{"customer_id":"nobody"}`)
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["config_version"]; ok {
t.Errorf("ACK should omit config_version for a report-only (no config) customer; got %v", ack["config_version"])
}
}