package agent import ( "context" "encoding/json" "log/slog" "net/http" "net/http/httptest" "testing" "git.wntrmute.dev/mc/mcp/internal/config" ) func TestNilDNSRegistrarIsNoop(t *testing.T) { var d *DNSRegistrar if err := d.EnsureRecord(context.Background(), "svc"); err != nil { t.Fatalf("EnsureRecord on nil: %v", err) } if err := d.RemoveRecord(context.Background(), "svc"); err != nil { t.Fatalf("RemoveRecord on nil: %v", err) } } func TestNewDNSRegistrarDisabledWhenUnconfigured(t *testing.T) { d, err := NewDNSRegistrar(config.MCNSConfig{}, slog.Default()) if err != nil { t.Fatalf("unexpected error: %v", err) } if d != nil { t.Fatal("expected nil registrar for empty config") } } func TestEnsureRecordCreatesWhenMissing(t *testing.T) { var gotMethod, gotPath, gotAuth string var gotBody map[string]interface{} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { // List returns empty — no existing records. w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"records":[]}`)) return } gotMethod = r.Method gotPath = r.URL.Path gotAuth = r.Header.Get("Authorization") _ = json.NewDecoder(r.Body).Decode(&gotBody) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"id":1}`)) })) defer srv.Close() d := &DNSRegistrar{ serverURL: srv.URL, token: "test-token", zone: "svc.mcp.metacircular.net", nodeAddr: "192.168.88.181", httpClient: srv.Client(), logger: slog.Default(), } if err := d.EnsureRecord(context.Background(), "myservice"); err != nil { t.Fatalf("EnsureRecord: %v", err) } if gotMethod != http.MethodPost { t.Fatalf("method: got %q, want POST", gotMethod) } if gotPath != "/v1/zones/svc.mcp.metacircular.net/records" { t.Fatalf("path: got %q", gotPath) } if gotAuth != "Bearer test-token" { t.Fatalf("auth: got %q", gotAuth) } if gotBody["name"] != "myservice" { t.Fatalf("name: got %v", gotBody["name"]) } if gotBody["type"] != "A" { t.Fatalf("type: got %v", gotBody["type"]) } if gotBody["value"] != "192.168.88.181" { t.Fatalf("value: got %v", gotBody["value"]) } } func TestEnsureRecordSkipsWhenExists(t *testing.T) { createCalled := false 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. 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(resp) return } createCalled = true w.WriteHeader(http.StatusCreated) })) defer srv.Close() d := &DNSRegistrar{ serverURL: srv.URL, token: "test-token", zone: "svc.mcp.metacircular.net", nodeAddr: "192.168.88.181", httpClient: srv.Client(), logger: slog.Default(), } if err := d.EnsureRecord(context.Background(), "myservice"); err != nil { t.Fatalf("EnsureRecord: %v", err) } if createCalled { t.Fatal("should not create when record already exists with correct value") } } func TestEnsureRecordUpdatesWrongValue(t *testing.T) { var gotMethod string var gotPath string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { // Return a record with a stale value. 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(resp) return } gotMethod = r.Method gotPath = r.URL.Path w.WriteHeader(http.StatusOK) })) defer srv.Close() d := &DNSRegistrar{ serverURL: srv.URL, token: "test-token", zone: "svc.mcp.metacircular.net", nodeAddr: "192.168.88.181", httpClient: srv.Client(), logger: slog.Default(), } if err := d.EnsureRecord(context.Background(), "myservice"); err != nil { t.Fatalf("EnsureRecord: %v", err) } if gotMethod != http.MethodPut { t.Fatalf("method: got %q, want PUT", gotMethod) } if gotPath != "/v1/zones/svc.mcp.metacircular.net/records/42" { t.Fatalf("path: got %q", gotPath) } } func TestRemoveRecordDeletes(t *testing.T) { var gotMethod, gotPath string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { 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(resp) return } gotMethod = r.Method gotPath = r.URL.Path w.WriteHeader(http.StatusNoContent) })) defer srv.Close() d := &DNSRegistrar{ serverURL: srv.URL, token: "test-token", zone: "svc.mcp.metacircular.net", nodeAddr: "192.168.88.181", httpClient: srv.Client(), logger: slog.Default(), } if err := d.RemoveRecord(context.Background(), "myservice"); err != nil { t.Fatalf("RemoveRecord: %v", err) } if gotMethod != http.MethodDelete { t.Fatalf("method: got %q, want DELETE", gotMethod) } if gotPath != "/v1/zones/svc.mcp.metacircular.net/records/7" { t.Fatalf("path: got %q", gotPath) } } 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(`{"records":[]}`)) })) defer srv.Close() d := &DNSRegistrar{ serverURL: srv.URL, token: "test-token", zone: "svc.mcp.metacircular.net", nodeAddr: "192.168.88.181", httpClient: srv.Client(), logger: slog.Default(), } if err := d.RemoveRecord(context.Background(), "myservice"); err != nil { t.Fatalf("RemoveRecord: %v", err) } }