package harness import ( "encoding/json" "fmt" "reflect" "strconv" "strings" "time" ) func timeoutMS(ms int) time.Duration { if ms <= 0 { ms = 5000 } return time.Duration(ms) * time.Millisecond } func valueAt(v any, path string) (any, bool) { if path == "" { return v, true } cur := v for _, part := range strings.Split(path, ".") { switch x := cur.(type) { case map[string]any: next, ok := x[part] if !ok { return nil, false } cur = next case []any: i, err := strconv.Atoi(part) if err != nil || i < 0 || i >= len(x) { return nil, false } cur = x[i] default: return nil, false } } return cur, true } func assertJSONValue(raw json.RawMessage, path string, equals any, contains string, allowSubstring bool) error { var v any if err := json.Unmarshal(raw, &v); err != nil { return err } got, ok := valueAt(v, path) if !ok { return fmt.Errorf("path %q not found in %s", path, string(raw)) } if equals != nil { if !reflect.DeepEqual(normalizeJSON(equals), normalizeJSON(got)) { return fmt.Errorf("path %q = %#v, want %#v", path, got, equals) } } if contains != "" { switch x := got.(type) { case string: if !strings.Contains(x, contains) { return fmt.Errorf("path %q = %q, does not contain %q", path, x, contains) } default: b, _ := json.Marshal(x) if allowSubstring && strings.Contains(string(b), contains) { return nil } return fmt.Errorf("path %q is not a string: %#v", path, got) } } return nil } func normalizeJSON(v any) any { switch x := v.(type) { case int: return float64(x) case int64: return float64(x) case []any: for i := range x { x[i] = normalizeJSON(x[i]) } case map[string]any: for k := range x { x[k] = normalizeJSON(x[k]) } } return v }