package classify import ( "encoding/json" "testing" ) func TestNode_ValidateAcceptsWellFormedTree(t *testing.T) { n := Node{Match: MatchAll, Conditions: []Node{ {Metric: "avg_pace_sec_per_km", Op: OpBetween, Value: []any{270.0, 300.0}}, {Match: MatchAny, Conditions: []Node{ {Metric: "aerobic_training_effect", Op: OpGte, Value: 3.5}, {Metric: "lap_interval_pattern", Op: OpEq, Value: true}, }}, }} if err := n.Validate(); err != nil { t.Errorf("Validate() = %v, want nil", err) } } func TestNode_ValidateRejectsMalformed(t *testing.T) { cases := []struct { name string n Node }{ {"unknown match", Node{Match: "xor", Conditions: []Node{{Metric: "x", Op: OpGt, Value: 1.0}}}}, {"branch with no conditions", Node{Match: MatchAll}}, {"leaf with no metric", Node{Op: OpGt, Value: 1.0}}, {"between with non-array value", Node{Metric: "x", Op: OpBetween, Value: 5.0}}, {"between with wrong-length array", Node{Metric: "x", Op: OpBetween, Value: []any{1.0}}}, {"unsupported op", Node{Metric: "x", Op: "~=", Value: 1.0}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if err := c.n.Validate(); err == nil { t.Error("Validate() = nil, want an error") } }) } } func TestNode_RoundTripsThroughJSON(t *testing.T) { raw := `{ "match": "all", "conditions": [ {"metric": "avg_pace_sec_per_km", "op": "between", "value": [270, 300]}, {"metric": "lap_interval_pattern", "op": "==", "value": true} ] }` var n Node if err := json.Unmarshal([]byte(raw), &n); err != nil { t.Fatalf("Unmarshal: %v", err) } if err := n.Validate(); err != nil { t.Fatalf("Validate() after round-trip = %v", err) } matched, _ := n.Evaluate(MetricContext{"avg_pace_sec_per_km": 285, "lap_interval_pattern": 1}) if !matched { t.Error("expected match after JSON round-trip") } }