From 2bda7fc138cfbcf38c43e1452be5d09bd49501c4 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 27 Mar 2026 15:12:43 -0700 Subject: [PATCH] Fix DNS record JSON parsing for MCNS response format MCNS returns records wrapped in {"records": [...]} envelope with uppercase field names (ID, Name, Type, Value), not bare arrays with lowercase fields. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/agent/dns.go | 18 ++++++++++-------- internal/agent/dns_test.go | 16 ++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/internal/agent/dns.go b/internal/agent/dns.go index 875161e..8d95a4f 100644 --- a/internal/agent/dns.go +++ b/internal/agent/dns.go @@ -28,11 +28,11 @@ type DNSRegistrar struct { // dnsRecord is the JSON representation of an MCNS record. type dnsRecord struct { - ID int `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Value string `json:"value"` - TTL int `json:"ttl"` + ID int `json:"ID"` + Name string `json:"Name"` + Type string `json:"Type"` + Value string `json:"Value"` + TTL int `json:"TTL"` } // NewDNSRegistrar creates a DNSRegistrar. Returns (nil, nil) if @@ -157,11 +157,13 @@ func (d *DNSRegistrar) listRecords(ctx context.Context, serviceName string) ([]d return nil, fmt.Errorf("list records: mcns returned %d: %s", resp.StatusCode, string(body)) } - var records []dnsRecord - if err := json.Unmarshal(body, &records); err != nil { + var envelope struct { + Records []dnsRecord `json:"records"` + } + if err := json.Unmarshal(body, &envelope); err != nil { return nil, fmt.Errorf("parse list response: %w", err) } - return records, nil + return envelope.Records, nil } // createRecord creates an A record in the zone. diff --git a/internal/agent/dns_test.go b/internal/agent/dns_test.go index df48cb4..5b57df0 100644 --- a/internal/agent/dns_test.go +++ b/internal/agent/dns_test.go @@ -39,7 +39,7 @@ func TestEnsureRecordCreatesWhenMissing(t *testing.T) { if r.Method == http.MethodGet { // List returns empty — no existing records. w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte("[]")) + _, _ = w.Write([]byte(`{"records":[]}`)) return } gotMethod = r.Method @@ -90,9 +90,9 @@ func TestEnsureRecordSkipsWhenExists(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { // Return an existing record with the correct value. - records := []dnsRecord{{ID: 1, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}} + resp := map[string][]dnsRecord{"records": {{ID: 1, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}}} w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(records) + _ = json.NewEncoder(w).Encode(resp) return } createCalled = true @@ -124,9 +124,9 @@ func TestEnsureRecordUpdatesWrongValue(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { // Return a record with a stale value. - records := []dnsRecord{{ID: 42, Name: "myservice", Type: "A", Value: "10.0.0.1", TTL: 300}} + resp := map[string][]dnsRecord{"records": {{ID: 42, Name: "myservice", Type: "A", Value: "10.0.0.1", TTL: 300}}} w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(records) + _ = json.NewEncoder(w).Encode(resp) return } gotMethod = r.Method @@ -160,9 +160,9 @@ func TestRemoveRecordDeletes(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { - records := []dnsRecord{{ID: 7, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}} + resp := map[string][]dnsRecord{"records": {{ID: 7, Name: "myservice", Type: "A", Value: "192.168.88.181", TTL: 300}}} w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(records) + _ = json.NewEncoder(w).Encode(resp) return } gotMethod = r.Method @@ -195,7 +195,7 @@ func TestRemoveRecordNoopWhenMissing(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // List returns empty. w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte("[]")) + _, _ = w.Write([]byte(`{"records":[]}`)) })) defer srv.Close()