package hub import ( "encoding/json" "os" "testing" ) // The desired-state wire is a contract DUPLICATED across two repos (no shared types module yet). // testdata/desired-state.golden.json and testdata/control-envelope.golden.json MUST be kept // byte-identical with felhom.eu/hub's copies; these tests decode them through the agent structs // and key-set-compare, catching drift between the struct and the served shape. func TestDesiredStateGolden_DecodesAndKeySet(t *testing.T) { raw, err := os.ReadFile("testdata/desired-state.golden.json") if err != nil { t.Fatal(err) } var resp DesiredStateResponse if err := json.Unmarshal(raw, &resp); err != nil { t.Fatalf("golden does not decode into DesiredStateResponse: %v", err) } if resp.Generation != 4 { t.Errorf("generation = %d, want 4", resp.Generation) } if len(resp.DesiredState.Guests) != 2 { t.Fatalf("guests = %d, want 2", len(resp.DesiredState.Guests)) } benign := resp.DesiredState.Guests[0] if benign.VMID != 100 || benign.Run != "running" || benign.Spec == nil || benign.Spec.Cores != 2 || benign.Description == nil { t.Errorf("benign guest = %+v", benign) } destructive := resp.DesiredState.Guests[1] if destructive.VMID != 200 || !destructive.Decommission { t.Errorf("destructive guest = %+v, want vmid 200 decommission", destructive) } if resp.DesiredState.PBSNamespace != "felhom-cust-acme" { t.Errorf("pbs_namespace = %q", resp.DesiredState.PBSNamespace) } if resp.DesiredState.RestoreDirective == nil || resp.DesiredState.RestoreDirective.Mode != "guest_loss" { t.Errorf("restore_directive = %+v, want carried (guest_loss)", resp.DesiredState.RestoreDirective) } // Bidirectional key-set drift guard: the marshaled struct's keys must match the golden's // (top-level, the desired_state object, and a guest element). var golden map[string]any json.Unmarshal(raw, &golden) b, _ := json.Marshal(resp) var got map[string]any json.Unmarshal(b, &got) assertSameKeys(t, "", golden, got) assertSameKeys(t, "desired_state", golden["desired_state"], got["desired_state"]) assertSameKeys(t, "desired_state.guests[0]", firstElem(golden["desired_state"].(map[string]any)["guests"]), firstElem(got["desired_state"].(map[string]any)["guests"])) assertSameKeys(t, "desired_state.restore_directive", golden["desired_state"].(map[string]any)["restore_directive"], got["desired_state"].(map[string]any)["restore_directive"]) } func TestControlEnvelopeGolden_Decodes(t *testing.T) { raw, err := os.ReadFile("testdata/control-envelope.golden.json") if err != nil { t.Fatal(err) } var env ControlEnvelope if err := json.Unmarshal(raw, &env); err != nil { t.Fatalf("golden does not decode into ControlEnvelope: %v", err) } if env.Status != "ok" || env.PollIntervalSeconds == nil || *env.PollIntervalSeconds != 900 { t.Errorf("envelope poll/status = %+v", env) } if env.DesiredGeneration != 4 || !env.HasSignedOps || env.Blocked { t.Errorf("envelope flags = gen %d signed %v blocked %v", env.DesiredGeneration, env.HasSignedOps, env.Blocked) } }