123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 | package config
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLoadSave(t *testing.T) {
dir := filepath.Join(t.TempDir(), "blinker")
c, err := Load(dir)
if err != nil || c.CommandTimeout != Duration(2*time.Minute) || c.Configured() || len(c.Sandbox.ProxyPorts) != 2 {
t.Fatalf("missing files: %+v, %v", c, err)
}
c.Model, c.APIKey, c.Sandbox.ScratchRoot = "m", "sk-1", "/scratch"
if err := c.Save(dir); err != nil {
t.Fatal(err)
}
fi, err := os.Stat(filepath.Join(dir, AgentFile))
if err != nil || fi.Mode().Perm() != 0o600 {
t.Errorf("mode = %v, %v", fi.Mode(), err)
}
got, err := Load(dir)
if err != nil || got.Model != "m" || got.APIKey != "sk-1" || !got.Configured() || got.Sandbox.ScratchRoot != "/scratch" {
t.Errorf("round trip: %+v, %v", got, err)
}
// The sandbox's file holds only its settings: no key.
data, err := os.ReadFile(filepath.Join(dir, SandboxFile))
if err != nil || !strings.Contains(string(data), "/scratch") || strings.Contains(string(data), "sk-1") || strings.Contains(string(data), "model") {
t.Errorf("%s: %s, %v", SandboxFile, data, err)
}
if s, err := LoadSandbox(dir); err != nil || s.ScratchRoot != "/scratch" {
t.Errorf("sandbox settings: %+v, %v", s, err)
}
}
func TestLoadRejects(t *testing.T) {
for name, text := range map[string]string{
"unknown": `{"modle": "typo"}`,
"duration": `{"command_timeout": 5}`,
"param": `{"thinking_param": "yes"}`,
"sandbox": `{"proxy_ports": [443]}`, // in the sandbox's file
"diff": `{"diff_command": [""]}`,
"url": `{"base_url": "localhost:8080/v1"}`,
"scheme": `{"base_url": "file:///etc/passwd"}`,
"temp": `{"temperature": 3}`,
"top_p": `{"top_p": 1.5}`,
"top_k": `{"top_k": -1}`,
"tokens": `{"max_tokens": -1}`,
"ca_cert": `{"ca_cert": "cert.pem"}`,
"log_file": `{"log_file": "log.txt"}`,
} {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, AgentFile), []byte(text), 0o600); err != nil {
t.Fatal(err)
}
if _, err := Load(dir); err == nil {
t.Errorf("%s: loaded %s", name, text)
}
}
for name, text := range map[string]string{
"unknown": `{"model": "m"}`, // in the agent's file
"floor": `{"extra_blocklist": ["relative/path"]}`,
"port": `{"proxy_ports": [70000]}`,
"scratch": `{"scratch_root": "tmp"}`,
} {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, SandboxFile), []byte(text), 0o600); err != nil {
t.Fatal(err)
}
if _, err := LoadSandbox(dir); err == nil {
t.Errorf("sandbox %s: loaded %s", name, text)
}
if _, err := Load(dir); err == nil {
t.Errorf("agent with sandbox %s: loaded %s", name, text)
}
}
}
func TestProjectName(t *testing.T) {
a, b := projectName("/home/u/my proj", "/home/u"), projectName("/home/u/my-proj", "/home/u")
if a == b || !strings.HasPrefix(a, "my-proj-") || len(a) != len("my-proj-")+8 {
t.Errorf("names %q, %q", a, b)
}
// Outside home, the whole path; the hash tells apart paths that look
// the same.
for _, c := range []struct{ project, prefix string }{
{"/opt/x", "opt-x-"},
{"/home/u2/x", "home-u2-x-"},
{"/home/u", "home-"},
{"/", "root-"},
} {
if n := projectName(c.project, "/home/u"); !strings.HasPrefix(n, c.prefix) {
t.Errorf("%s: %q", c.project, n)
}
}
if projectName("/home/u/opt/x", "/home/u") == projectName("/opt/x", "/home/u") {
t.Error("~/opt/x and /opt/x share a name")
}
if long := projectName("/"+strings.Repeat("x", 200), "/home/u"); len(long) > 90 {
t.Errorf("long name %q", long)
}
}
// Anthropic's key goes to Anthropic's host only, not to any URL that
// mentions it.
func TestKeyHost(t *testing.T) {
t.Setenv("ANTHROPIC_API_KEY", "sk-ant")
t.Setenv("OPENAI_API_KEY", "sk-oai")
for url, want := range map[string]string{
"https://api.anthropic.com/v1": "sk-ant",
"https://api.anthropic.com.evil.example/v1": "sk-oai",
"https://evil.example/api.anthropic.com/v1": "sk-oai",
"https://evil.example/v1?x=api.anthropic.com": "sk-oai",
"http://api.anthropic.com/v1": "sk-oai",
} {
if got := (Config{BaseURL: url}).Key(); got != want {
t.Errorf("%s: key %q, want %q", url, got, want)
}
}
}
func TestValidateModelName(t *testing.T) {
c := Defaults()
for _, m := range []string{"qwen\x1b]52;c;eA==\x07", "a\u202eb", "x\ny"} {
c.Model = m
if c.Validate() == nil {
t.Errorf("model %q passed", m)
}
}
}
// A config holding a key that others can read is refused.
func TestLoadRefusesReadableKey(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, AgentFile)
c := Defaults()
c.APIKey = "sk-1"
if err := c.Save(dir); err != nil {
t.Fatal(err)
}
if _, err := Load(dir); err != nil {
t.Fatalf("a private config: %v", err)
}
if err := os.Chmod(path, 0o644); err != nil {
t.Fatal(err)
}
if _, err := Load(dir); err == nil || !strings.Contains(err.Error(), "chmod 600") {
t.Errorf("a readable key: %v", err)
}
c.APIKey = ""
if err := c.Save(dir); err != nil {
t.Fatal(err)
}
if err := os.Chmod(path, 0o644); err != nil {
t.Fatal(err)
}
if _, err := Load(dir); err != nil {
t.Errorf("a readable config without a key: %v", err)
}
}
|