a3ac6c9488
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
72 lines
2.6 KiB
Go
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"])
|
|
}
|
|
}
|