add black-box debug harness
This commit is contained in:
92
internal/harness/observe.go
Normal file
92
internal/harness/observe.go
Normal file
@@ -0,0 +1,92 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user