package agents import ( "os" "path/filepath" "testing" ) func writeConfig(t *testing.T, body string) string { t.Helper() p := filepath.Join(t.TempDir(), "agents.json") if err := os.WriteFile(p, []byte(body), 0o600); err != nil { t.Fatal(err) } return p } func TestLoadConfigHappyPath(t *testing.T) { root := t.TempDir() cfg, err := LoadConfig(writeConfig(t, `{"groups":{"th1":{"roots":["`+root+`"]}}}`)) if err != nil { t.Fatal(err) } g := cfg.Groups["th1"] if len(g.Roots) != 1 || g.Roots[0] != root || g.Plain { t.Errorf("group: %+v", g) } if cfg.Command != "claude" { t.Errorf("command default: %q", cfg.Command) } } // Fail-closed: the config is the blast radius, so every broken shape must be // an error, never a silent empty (or widened) grant. func TestLoadConfigFailsClosed(t *testing.T) { missingDir := filepath.Join(t.TempDir(), "nope") cases := map[string]string{ "empty groups": `{"groups":{}}`, "broken json": `{"groups":`, "no roots no plain": `{"groups":{"th1":{"roots":[]}}}`, "empty root": `{"groups":{"th1":{"roots":[""]}}}`, "missing root dir": `{"groups":{"th1":{"roots":["` + missingDir + `"]}}}`, } for name, body := range cases { if _, err := LoadConfig(writeConfig(t, body)); err == nil { t.Errorf("%s: LoadConfig accepted it", name) } } if _, err := LoadConfig(filepath.Join(t.TempDir(), "absent.json")); err == nil { t.Error("missing file: LoadConfig accepted it") } } func TestLoadConfigPlainNeedsNoRoots(t *testing.T) { cfg, err := LoadConfig(writeConfig(t, `{"groups":{"th1":{"plain":true}}}`)) if err != nil { t.Fatal(err) } if !cfg.Groups["th1"].Plain { t.Error("plain not kept") } }