harness.toml loading via a stdlib-only TOML subset parser (tables, bare
keys, strings/ints/bools, multi-line arrays; anything richer fails loudly).
Secrets are refs only (env:/file:/literal:, bw: reserved) and are redacted
from every error path. Model routing v0: static class -> tier alias ->
concrete model map per the DESIGN model-selection layer; requests carry the
resolved concrete model and unknown classes fail hard so routing stays
auditable. Table-driven tests cover the parser, validation, key refs, and
routing decisions.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
140 lines
3.6 KiB
Go
140 lines
3.6 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseTOMLValid(t *testing.T) {
|
|
src := `
|
|
# top comment
|
|
vertical = "demo" # trailing comment
|
|
work_root = "."
|
|
|
|
[loop]
|
|
max_rounds = 4
|
|
|
|
[redmine]
|
|
url = "https://rm.example.org"
|
|
scope_query_id = 42
|
|
class_field = "Class"
|
|
|
|
[litellm]
|
|
base_url = "http://h:4001"
|
|
timeout_secs = 30
|
|
max_retries = 3
|
|
|
|
[models]
|
|
mopac-study = "glm-4.7-flash" # hyphenated bare key
|
|
default_tier = "mopac-study"
|
|
|
|
[models.classes]
|
|
study = "mopac-study"
|
|
code = "mopac-code"
|
|
|
|
[tools.bash]
|
|
enabled = false
|
|
default = "deny"
|
|
allow = ["pwd", "ls *", "cat *"]
|
|
deny = [
|
|
"sudo *", # comment inside array
|
|
"ssh *",
|
|
]
|
|
|
|
[deep.nested.table]
|
|
flag = true
|
|
note = "quote \" and # inside string"
|
|
empty = []
|
|
`
|
|
doc, err := ParseTOML(src)
|
|
if err != nil {
|
|
t.Fatalf("ParseTOML: %v", err)
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
got any
|
|
want any
|
|
}{
|
|
{"vertical", str(doc.String("vertical")), "demo"},
|
|
{"work_root", str(doc.String("work_root")), "."},
|
|
{"max_rounds", intg(doc.Table("loop").Int("max_rounds")), int64(4)},
|
|
{"scope_query_id", intg(doc.Table("redmine").Int("scope_query_id")), int64(42)},
|
|
{"class_field", str(doc.Table("redmine").String("class_field")), "Class"},
|
|
{"timeout_secs", intg(doc.Table("litellm").Int("timeout_secs")), int64(30)},
|
|
{"max_retries", intg(doc.Table("litellm").Int("max_retries")), int64(3)},
|
|
{"tier", str(doc.Table("models").String("mopac-study")), "glm-4.7-flash"},
|
|
{"default_tier", str(doc.Table("models").String("default_tier")), "mopac-study"},
|
|
{"class map", str(doc.Table("models", "classes").String("study")), "mopac-study"},
|
|
{"bash enabled", boolean(doc.Table("tools", "bash").Bool("enabled")), false},
|
|
{"deep bool", boolean(doc.Table("deep", "nested", "table").Bool("flag")), true},
|
|
{"escaped string", str(doc.Table("deep", "nested", "table").String("note")), `quote " and # inside string`},
|
|
}
|
|
for _, tc := range tests {
|
|
if tc.got != tc.want {
|
|
t.Errorf("%s = %v, want %v", tc.name, tc.got, tc.want)
|
|
}
|
|
}
|
|
|
|
allow, ok := doc.Table("tools", "bash").StringList("allow")
|
|
if !ok || len(allow) != 3 || allow[1] != "ls *" {
|
|
t.Errorf("allow list = %v ok=%v", allow, ok)
|
|
}
|
|
deny, ok := doc.Table("tools", "bash").StringList("deny")
|
|
if !ok || len(deny) != 2 || deny[0] != "sudo *" {
|
|
t.Errorf("deny list = %v ok=%v", deny, ok)
|
|
}
|
|
if empty, ok := doc.Table("deep", "nested", "table").StringList("empty"); !ok || len(empty) != 0 {
|
|
t.Errorf("empty array = %v ok=%v", empty, ok)
|
|
}
|
|
}
|
|
|
|
func str(v string, ok bool) string {
|
|
if !ok {
|
|
panic("string key missing")
|
|
}
|
|
return v
|
|
}
|
|
|
|
func intg(v int64, ok bool) int64 {
|
|
if !ok {
|
|
panic("int key missing")
|
|
}
|
|
return v
|
|
}
|
|
|
|
func boolean(v bool, ok bool) bool {
|
|
if !ok {
|
|
panic("bool key missing")
|
|
}
|
|
return v
|
|
}
|
|
|
|
func TestParseTOMLErrors(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
src string
|
|
want string
|
|
}{
|
|
{"unterminated string", `a = "oops`, "unterminated string"},
|
|
{"bare value", `a = sk-123`, "unsupported value"},
|
|
{"missing value", `a =`, "missing value"},
|
|
{"bad key", `a b = "c"`, "invalid key"},
|
|
{"duplicate key", "a = \"1\"\na = \"2\"", "duplicate key"},
|
|
{"scalar to table", "a = \"1\"\n[a.b]", "cannot become table"},
|
|
{"unterminated array", "a = [\n\"x\",", "unterminated array"},
|
|
{"bad table name", `[a.]`, "invalid table name"},
|
|
{"no equals", `[t]\njustakey`, "expected key = value"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := ParseTOML(strings.ReplaceAll(tc.src, `\n`, "\n"))
|
|
if err == nil {
|
|
t.Fatalf("expected error containing %q, got nil", tc.want)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("error %q does not contain %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|