package config import ( "encoding/json" "testing" "time" ) // Absent blocks must read as off on a nil receiver: the daemon calls these // helpers before it knows whether the operator configured anything. func TestSensesOffByDefault(t *testing.T) { var cfg Config if cfg.Media.StoreDir() != "" { t.Error("media store dir is set with no media block") } if cfg.Vision.LooksAtImages() { t.Error("vision is on with no vision block") } } // enabled with nothing to talk to is a misconfiguration, not a capability. func TestVisionNeedsBothEnabledAndEndpoint(t *testing.T) { cases := []struct { name string v *VisionConfig want bool }{ {"absent", nil, false}, {"endpoint but not enabled", &VisionConfig{Endpoint: "http://127.0.0.1:8081"}, false}, {"enabled but no endpoint", &VisionConfig{Enabled: true}, false}, {"enabled, blank endpoint", &VisionConfig{Enabled: true, Endpoint: " "}, false}, {"both", &VisionConfig{Enabled: true, Endpoint: "http://127.0.0.1:8081"}, true}, } for _, c := range cases { if got := c.v.LooksAtImages(); got != c.want { t.Errorf("%s: LooksAtImages() = %v, want %v", c.name, got, c.want) } } } func TestSensesBlocksParseFromJSON(t *testing.T) { raw := `{ "db_path": "/tmp/x.db", "socket_path": "/tmp/x.sock", "media": {"dir": "media", "retention": "48h", "max_bytes": 1048576}, "vision": { "enabled": true, "endpoint": "http://127.0.0.1:8081", "model": "qwen2.5-vl", "max_dim": 640, "max_tokens": 200, "timeout": "45s", "prompt": "Что тут?" } }` var cfg Config if err := json.Unmarshal([]byte(raw), &cfg); err != nil { t.Fatalf("unmarshal: %v", err) } if cfg.Media.StoreDir() != "media" { t.Errorf("media dir = %q", cfg.Media.StoreDir()) } if time.Duration(cfg.Media.Retention) != 48*time.Hour { t.Errorf("retention = %v", time.Duration(cfg.Media.Retention)) } if cfg.Media.MaxBytes != 1<<20 { t.Errorf("max_bytes = %d", cfg.Media.MaxBytes) } if !cfg.Vision.LooksAtImages() { t.Fatal("vision did not parse as enabled") } if cfg.Vision.MaxDim != 640 || cfg.Vision.MaxTokens != 200 { t.Errorf("vision limits = %+v", cfg.Vision) } if time.Duration(cfg.Vision.Timeout) != 45*time.Second { t.Errorf("vision timeout = %v", time.Duration(cfg.Vision.Timeout)) } if cfg.Vision.Prompt != "Что тут?" { t.Errorf("prompt = %q", cfg.Vision.Prompt) } } // A media dir set with no vision block is a valid state, and the useful one on a // box with no vision model: images can be kept, they just cannot be described. func TestMediaWithoutVisionIsValid(t *testing.T) { var cfg Config if err := json.Unmarshal([]byte(`{"media":{"dir":"/srv/media"}}`), &cfg); err != nil { t.Fatal(err) } if cfg.Media.StoreDir() != "/srv/media" { t.Errorf("dir = %q", cfg.Media.StoreDir()) } if cfg.Vision.LooksAtImages() { t.Error("vision came on by itself") } }