181 lines
5.2 KiB
Go
181 lines
5.2 KiB
Go
package agentconfig
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadValidCatalogNormalizesDeterministicOrder(t *testing.T) {
|
|
catalog, err := Load(filepath.Join("testdata", "valid.yaml"))
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if got, want := catalog.Providers[0].VersionProbe.TimeoutMS, DefaultProbeTimeoutMS; got != want {
|
|
t.Fatalf("version timeout = %d, want %d", got, want)
|
|
}
|
|
if got, want := catalog.Profiles[0].MaxConcurrency, 1; got != want {
|
|
t.Fatalf("max concurrency = %d, want %d", got, want)
|
|
}
|
|
if got, want := catalog.Profiles[0].Capabilities, []string{"cancel", "resume", "run", "status"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("capabilities = %v, want %v", got, want)
|
|
}
|
|
resolved, ok := catalog.ResolveProfile("codex-test")
|
|
if !ok {
|
|
t.Fatal("ResolveProfile returned false")
|
|
}
|
|
if resolved.Provider.ID != "codex" || resolved.Model.ID != "gpt-test" || resolved.Profile.ID != "codex-test" {
|
|
t.Fatalf("resolved identity = %#v", resolved)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsInvalidCatalogFixtures(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
file string
|
|
want string
|
|
}{
|
|
{name: "duplicate provider", file: "duplicate-provider.yaml", want: "duplicate provider id"},
|
|
{name: "dangling model", file: "dangling-model.yaml", want: "references unknown provider"},
|
|
{name: "dangling profile", file: "dangling-profile.yaml", want: "references unknown provider"},
|
|
{name: "invalid capability", file: "invalid-capability.yaml", want: "invalid capability"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
_, err := Load(filepath.Join("testdata", test.file))
|
|
if err == nil || !strings.Contains(err.Error(), test.want) {
|
|
t.Fatalf("Load error = %v, want containing %q", err, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsProfileReferenceAndSecretEnvErrors(t *testing.T) {
|
|
base := Catalog{
|
|
Version: SchemaVersion,
|
|
Providers: []Provider{{
|
|
ID: "codex",
|
|
Command: "codex",
|
|
VersionProbe: CommandProbe{Args: []string{"--version"}},
|
|
Authentication: AuthenticationProbe{Args: []string{"login", "status"}},
|
|
Capabilities: []string{"run"},
|
|
}},
|
|
Models: []Model{{ID: "model-a", Provider: "codex", Target: "native-a"}},
|
|
Profiles: []Profile{{
|
|
ID: "profile-a",
|
|
Provider: "codex",
|
|
Model: "model-a",
|
|
Capabilities: []string{"run"},
|
|
}},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*Catalog)
|
|
want string
|
|
}{
|
|
{
|
|
name: "unknown model",
|
|
mutate: func(c *Catalog) {
|
|
c.Profiles[0].Model = "missing"
|
|
},
|
|
want: "references unknown model",
|
|
},
|
|
{
|
|
name: "cross provider model",
|
|
mutate: func(c *Catalog) {
|
|
c.Providers = append(c.Providers, Provider{
|
|
ID: "claude",
|
|
Command: "claude",
|
|
VersionProbe: CommandProbe{Args: []string{"--version"}},
|
|
Authentication: AuthenticationProbe{Args: []string{"auth", "status"}},
|
|
Capabilities: []string{"run"},
|
|
})
|
|
c.Profiles[0].Provider = "claude"
|
|
},
|
|
want: "does not own model",
|
|
},
|
|
{
|
|
name: "secret env",
|
|
mutate: func(c *Catalog) {
|
|
c.Profiles[0].Env = []string{"API_TOKEN=do-not-track"}
|
|
},
|
|
want: "may contain a tracked secret",
|
|
},
|
|
{
|
|
name: "invalid auth regex",
|
|
mutate: func(c *Catalog) {
|
|
c.Providers[0].Authentication.SuccessPattern = "["
|
|
},
|
|
want: "success_pattern",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
catalog := base
|
|
catalog.Providers = append([]Provider(nil), base.Providers...)
|
|
catalog.Models = append([]Model(nil), base.Models...)
|
|
catalog.Profiles = append([]Profile(nil), base.Profiles...)
|
|
test.mutate(&catalog)
|
|
err := catalog.Validate()
|
|
if err == nil || !strings.Contains(err.Error(), test.want) {
|
|
t.Fatalf("Validate error = %v, want containing %q", err, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsUnknownFieldsAndMultipleDocuments(t *testing.T) {
|
|
for name, content := range map[string]string{
|
|
"unknown": `
|
|
version: "1"
|
|
unexpected: true
|
|
providers: []
|
|
models: []
|
|
profiles: []
|
|
`,
|
|
"multiple": `
|
|
version: "1"
|
|
providers: []
|
|
models: []
|
|
profiles: []
|
|
---
|
|
version: "1"
|
|
`,
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "catalog.yaml")
|
|
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
if _, err := Load(path); err == nil {
|
|
t.Fatal("Load unexpectedly succeeded")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateAcceptsWritableRootConfinementCapability(t *testing.T) {
|
|
catalog := Catalog{
|
|
Version: SchemaVersion,
|
|
Providers: []Provider{{
|
|
ID: "codex",
|
|
Command: "codex",
|
|
VersionProbe: CommandProbe{Args: []string{"--version"}},
|
|
Authentication: AuthenticationProbe{Args: []string{"login", "status"}},
|
|
Capabilities: []string{"approval_bypass", "run", "unattended", "writable_root_confinement"},
|
|
}},
|
|
Models: []Model{{ID: "model-a", Provider: "codex", Target: "native-a"}},
|
|
Profiles: []Profile{{
|
|
ID: "profile-a",
|
|
Provider: "codex",
|
|
Model: "model-a",
|
|
Capabilities: []string{"approval_bypass", "run", "unattended", "writable_root_confinement"},
|
|
}},
|
|
}
|
|
if err := catalog.Validate(); err != nil {
|
|
t.Fatalf("Validate guardrail capability: %v", err)
|
|
}
|
|
}
|