config: keyproxy mpk refs, loop daemon knobs, redmine status map, gitea section
mpk: key refs resolve through the ukrrs/mopac-keyproxy hop ([keyproxy]
url + token_ref, POST /v1/resolve, short in-memory cache); env:/file:/
literal: refs keep working untouched, so the harness runs with or
without keyproxy up. [loop] gains poll_interval_secs + state_dir for the
self-host daemon; [redmine.status_map] (quoted TOML keys) drives status
transitions; [gitea] carries the optional REPORT-commit step, off by
default. All hosts live in harness.toml, none in code.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -123,7 +123,7 @@ func TestLoadValidationErrors(t *testing.T) {
|
||||
{
|
||||
name: "bare key value",
|
||||
mut: func(s string) string { return strings.Replace(s, `key_ref = "env:HARNESS_TEST_LLM_KEY"`, `key_ref = "sk-or-whatever"`, 1) },
|
||||
want: "env:, file:, literal:, or bw:",
|
||||
want: "env:, file:, literal:, mpk:, or bw:",
|
||||
},
|
||||
{
|
||||
name: "bw ref not implemented",
|
||||
@@ -289,3 +289,144 @@ secret_ref = "HARNESS_GITEA_HOOK"
|
||||
t.Errorf("error leaks ref value: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoopAndKeyProxyAndGiteaParsed(t *testing.T) {
|
||||
cfg, err := Load(writeTemp(t, `
|
||||
vertical = "demo"
|
||||
[litellm]
|
||||
base_url = "http://x:4001"
|
||||
key_ref = "mpk:mpk-litellm"
|
||||
[models]
|
||||
mopac-primary = "glm-5.3"
|
||||
|
||||
[loop]
|
||||
max_rounds = 6
|
||||
poll_interval_secs = 45
|
||||
state_dir = "st/loop"
|
||||
|
||||
[redmine]
|
||||
url = "https://rm.test"
|
||||
key_ref = "mpk:redmine"
|
||||
scope_query = "project=x"
|
||||
|
||||
[redmine.status_map]
|
||||
"In Progress" = "Done"
|
||||
New = "In Progress"
|
||||
|
||||
[keyproxy]
|
||||
url = "http://127.0.0.1:8082"
|
||||
token_ref = "literal:kp-token"
|
||||
cache_ttl_secs = 30
|
||||
|
||||
[gitea]
|
||||
url = "https://git.test"
|
||||
key_ref = "mpk:gitea"
|
||||
owner = "ukrrs"
|
||||
repo = "MOPAC"
|
||||
branch = "reports"
|
||||
commit_reports = true
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Loop.PollIntervalSecs != 45 || cfg.Loop.StateDir != "st/loop" || cfg.Loop.MaxRounds != 6 {
|
||||
t.Errorf("loop wrong: %+v", cfg.Loop)
|
||||
}
|
||||
if cfg.Redmine.StatusMap["In Progress"] != "Done" || cfg.Redmine.StatusMap["New"] != "In Progress" {
|
||||
t.Errorf("status map wrong: %+v", cfg.Redmine.StatusMap)
|
||||
}
|
||||
if cfg.KeyProxy.URL != "http://127.0.0.1:8082" || cfg.KeyProxy.TokenRef != "literal:kp-token" || cfg.KeyProxy.CacheTTLSecs != 30 {
|
||||
t.Errorf("keyproxy wrong: %+v", cfg.KeyProxy)
|
||||
}
|
||||
if !cfg.Gitea.CommitReports || cfg.Gitea.Owner != "ukrrs" || cfg.Gitea.Repo != "MOPAC" || cfg.Gitea.Branch != "reports" {
|
||||
t.Errorf("gitea wrong: %+v", cfg.Gitea)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoopDefaults(t *testing.T) {
|
||||
cfg, err := Load(writeTemp(t, `
|
||||
vertical = "demo"
|
||||
[litellm]
|
||||
base_url = "http://x:4001"
|
||||
key_ref = "literal:k"
|
||||
[models]
|
||||
mopac-primary = "glm-5.3"
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if cfg.Loop.PollIntervalSecs != 120 {
|
||||
t.Errorf("default poll_interval_secs = %d, want 120", cfg.Loop.PollIntervalSecs)
|
||||
}
|
||||
if cfg.Loop.StateDir != "state/loop" {
|
||||
t.Errorf("default loop state_dir = %q", cfg.Loop.StateDir)
|
||||
}
|
||||
if cfg.KeyProxy.URL != "" || cfg.Gitea.CommitReports {
|
||||
t.Errorf("keyproxy/gitea must default off: %+v %+v", cfg.KeyProxy, cfg.Gitea)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyProxyValidation(t *testing.T) {
|
||||
cases := []struct{ name, extra, want string }{
|
||||
{
|
||||
name: "token without url",
|
||||
extra: "[keyproxy]\ntoken_ref = \"literal:t\"\n",
|
||||
want: "[keyproxy]: url is required",
|
||||
},
|
||||
{
|
||||
name: "url without token",
|
||||
extra: "[keyproxy]\nurl = \"http://127.0.0.1:8082\"\n",
|
||||
want: "[keyproxy]: token_ref is required",
|
||||
},
|
||||
{
|
||||
name: "mpk token ref (recursion)",
|
||||
extra: "[keyproxy]\nurl = \"http://127.0.0.1:8082\"\ntoken_ref = \"mpk:self\"\n",
|
||||
want: "must be a local ref",
|
||||
},
|
||||
{
|
||||
name: "gitea on but incomplete",
|
||||
extra: "[gitea]\ncommit_reports = true\nurl = \"https://git.test\"\n",
|
||||
want: "[gitea]: url, key_ref, owner and repo are required",
|
||||
},
|
||||
{
|
||||
name: "loop poll too small",
|
||||
extra: "[loop]\npoll_interval_secs = 0\n",
|
||||
want: "[loop]: poll_interval_secs must be >= 1",
|
||||
},
|
||||
}
|
||||
base := `
|
||||
vertical = "demo"
|
||||
[litellm]
|
||||
base_url = "http://x:4001"
|
||||
key_ref = "literal:k"
|
||||
[models]
|
||||
mopac-primary = "glm-5.3"
|
||||
`
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := Load(writeTemp(t, base+tc.extra))
|
||||
if err == nil {
|
||||
t.Fatalf("expected error containing %q", tc.want)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("error %q does not contain %q", err, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckKeyRefMPK(t *testing.T) {
|
||||
for _, ref := range []string{"mpk:mpk-redmine", "mpk:redmine"} {
|
||||
if err := CheckKeyRef(ref); err != nil {
|
||||
t.Errorf("CheckKeyRef(%q) = %v, want nil", ref, err)
|
||||
}
|
||||
}
|
||||
if err := CheckKeyRef("mpk:"); err == nil {
|
||||
t.Errorf("CheckKeyRef(\"mpk:\") should fail")
|
||||
}
|
||||
// The local resolver must point mpk: users at the configured resolver.
|
||||
_, err := ResolveKeyRef("mpk:redmine")
|
||||
if err == nil || !strings.Contains(err.Error(), "[keyproxy]") {
|
||||
t.Errorf("local ResolveKeyRef on mpk: should name [keyproxy], got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user