package mcp import ( "encoding/json" "math" "testing" ) // TestSanitizeNonFiniteFloatsAssetResponseMarshals reproduces the integration // failure (TestMCPAssetVsHTTP: "marshaling output: json: unsupported value: // NaN") and verifies the sanitizer fixes it: encoding/json must reject the // response before sanitization and accept it after, with non-finite floats // zeroed and finite ones preserved. func TestSanitizeNonFiniteFloatsAssetResponseMarshals(t *testing.T) { usedBytes := math.NaN() resp := &AssetResponse{ Assets: map[string]*AssetSet{ "assets": &AssetSet{ Name: "assets", Assets: []*Asset{ &Asset{ Type: "Node", Minutes: math.NaN(), Adjustment: math.Inf(1), TotalCost: math.Inf(-1), CPUCost: math.NaN(), GPUCost: 5.0, // finite, must be preserved ByteHoursUsed: &usedBytes, Overhead: &NodeOverhead{OverheadCostFraction: math.NaN()}, CPUBreakdown: &AssetBreakdown{Idle: math.NaN()}, }, }, }, }, } if _, err := json.Marshal(resp); err == nil { t.Fatal("expected json.Marshal to fail before sanitization (NaN/Inf present)") } resp = sanitizeNonFiniteFloats(resp).(*AssetResponse) if _, err := json.Marshal(resp); err != nil { t.Fatalf("expected json.Marshal to succeed after sanitization, got %v", err) } a := resp.Assets["assets"].Assets[0] if a.Minutes != 0 || a.Adjustment != 0 || a.TotalCost != 0 || a.CPUCost != 0 { t.Fatalf("expected non-finite base floats zeroed, got %+v", a) } if a.GPUCost != 5.0 { t.Fatalf("expected finite GPUCost preserved, got %v", a.GPUCost) } if a.ByteHoursUsed == nil || *a.ByteHoursUsed != 0 { t.Fatalf("expected non-finite *float64 zeroed, got %v", a.ByteHoursUsed) } if a.Overhead.OverheadCostFraction != 0 { t.Fatalf("expected nested overhead fraction zeroed, got %v", a.Overhead.OverheadCostFraction) } if a.CPUBreakdown.Idle != 0 { t.Fatalf("expected nested breakdown value zeroed, got %v", a.CPUBreakdown.Idle) } } // TestSanitizeNonFiniteFloatsValueType verifies a non-pointer (value) input is // sanitized via the returned copy, not just pointers. func TestSanitizeNonFiniteFloatsValueType(t *testing.T) { in := Asset{TotalCost: math.NaN(), GPUCost: 3.0} out, ok := sanitizeNonFiniteFloats(in).(Asset) if !ok { t.Fatalf("expected Asset back, got %T", sanitizeNonFiniteFloats(in)) } if out.TotalCost != 0 { t.Fatalf("expected NaN zeroed in returned value, got %v", out.TotalCost) } if out.GPUCost != 3.0 { t.Fatalf("expected finite value preserved, got %v", out.GPUCost) } } func TestSanitizeNonFiniteFloatsNilSafe(t *testing.T) { if got := sanitizeNonFiniteFloats(nil); got != nil { t.Fatalf("expected nil, got %v", got) } var p *AssetResponse sanitizeNonFiniteFloats(p) sanitizeNonFiniteFloats(&AssetResponse{}) }