Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

477 lines
14 KiB
Go

package config
import (
"maps"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// validVectorConfig returns a VectorConfig with every field enabled
// and populated with values that pass Validate, so each table case
// below can mutate exactly the field under test.
func validVectorConfig() VectorConfig {
return VectorConfig{
Enabled: true,
Embeddings: VectorEmbeddingsConfig{
Model: "nomic-embed-text",
Dimension: 768,
MaxInputChars: 8192,
DefaultServer: "local",
Servers: map[string]VectorEmbeddingsServerConfig{
"local": {
Endpoint: "http://localhost:11434/v1",
Timeout: "30s",
BatchSize: 32,
Concurrency: 4,
MaxRetries: 3,
},
"remote": {
Endpoint: "http://build-box:30000/v1",
Timeout: "300s",
BatchSize: 32,
Concurrency: 6,
MaxRetries: 3,
},
},
},
Embed: VectorEmbedConfig{
BackstopInterval: "24h",
},
}
}
// mutateServer applies fn to the named server entry, working around map
// values not being addressable.
func mutateServer(c *VectorConfig, name string, fn func(*VectorEmbeddingsServerConfig)) {
s := c.Embeddings.Servers[name]
fn(&s)
c.Embeddings.Servers[name] = s
}
func TestVectorConfigValidate(t *testing.T) {
tests := []struct {
name string
mutate func(*VectorConfig)
wantErr string
}{
{
name: "disabled is valid even with empty fields",
mutate: func(c *VectorConfig) { *c = VectorConfig{} },
},
{
name: "enabled missing model",
mutate: func(c *VectorConfig) { c.Embeddings.Model = "" },
wantErr: "model is required",
},
{
name: "enabled missing dimension",
mutate: func(c *VectorConfig) { c.Embeddings.Dimension = 0 },
wantErr: "dimension",
},
{
name: "enabled negative dimension",
mutate: func(c *VectorConfig) { c.Embeddings.Dimension = -1 },
wantErr: "dimension",
},
{
name: "enabled zero max_input_chars",
mutate: func(c *VectorConfig) { c.Embeddings.MaxInputChars = 0 },
wantErr: "max_input_chars",
},
{
name: "enabled negative max_input_chars",
mutate: func(c *VectorConfig) { c.Embeddings.MaxInputChars = -1 },
wantErr: "max_input_chars",
},
{
name: "enabled with no servers",
mutate: func(c *VectorConfig) { c.Embeddings.Servers = nil },
wantErr: "at least one server",
},
{
name: "multiple servers without default_server",
mutate: func(c *VectorConfig) {
c.Embeddings.DefaultServer = ""
},
wantErr: "default_server is required",
},
{
name: "default_server names an undefined server",
mutate: func(c *VectorConfig) {
c.Embeddings.DefaultServer = "nope"
},
wantErr: `default_server "nope" is not a defined server`,
},
{
name: "single server needs no default_server",
mutate: func(c *VectorConfig) {
delete(c.Embeddings.Servers, "remote")
c.Embeddings.DefaultServer = ""
},
},
{
name: "server missing endpoint",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.Endpoint = "" })
},
wantErr: "[vector.embeddings.servers.local] endpoint is required",
},
{
name: "server zero batch_size",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.BatchSize = 0 })
},
wantErr: "batch_size",
},
{
name: "server negative batch_size",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.BatchSize = -1 })
},
wantErr: "batch_size",
},
{
name: "server zero concurrency",
mutate: func(c *VectorConfig) {
mutateServer(c, "remote", func(s *VectorEmbeddingsServerConfig) { s.Concurrency = 0 })
},
wantErr: "[vector.embeddings.servers.remote] concurrency",
},
{
name: "server negative max_retries",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.MaxRetries = -1 })
},
wantErr: "max_retries",
},
{
name: "server zero max_retries disables retries and is valid",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.MaxRetries = 0 })
},
},
{
name: "server bad timeout",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.Timeout = "not-a-duration" })
},
wantErr: "timeout",
},
{
name: "server zero timeout",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.Timeout = "0s" })
},
wantErr: "timeout",
},
{
name: "server negative timeout",
mutate: func(c *VectorConfig) {
mutateServer(c, "local", func(s *VectorEmbeddingsServerConfig) { s.Timeout = "-1s" })
},
wantErr: "timeout",
},
{
name: "enabled bad backstop interval",
mutate: func(c *VectorConfig) { c.Embed.BackstopInterval = "not-a-duration" },
wantErr: "backstop_interval",
},
{
name: "enabled explicit zero backstop interval is invalid",
mutate: func(c *VectorConfig) { c.Embed.BackstopInterval = "0s" },
wantErr: "use a negative value to disable",
},
{
name: "enabled negative backstop interval disables and is valid",
mutate: func(c *VectorConfig) { c.Embed.BackstopInterval = "-1s" },
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := validVectorConfig()
tt.mutate(&cfg)
err := cfg.Validate()
if tt.wantErr == "" {
assert.NoError(t, err)
return
}
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestVectorEmbeddingsServerResolution(t *testing.T) {
c := validVectorConfig().Embeddings
name, server, err := c.Server("")
require.NoError(t, err)
assert.Equal(t, "local", name, "empty name resolves to default_server")
assert.Equal(t, "http://localhost:11434/v1", server.Endpoint)
name, server, err = c.Server("remote")
require.NoError(t, err)
assert.Equal(t, "remote", name)
assert.Equal(t, "http://build-box:30000/v1", server.Endpoint)
_, _, err = c.Server("nope")
require.Error(t, err)
assert.Contains(t, err.Error(), `no server named "nope"`)
assert.Contains(t, err.Error(), "local, remote", "error lists the defined servers")
c.DefaultServer = ""
delete(c.Servers, "remote")
name, _, err = c.Server("")
require.NoError(t, err)
assert.Equal(t, "local", name, "a single server is the implicit default")
}
func TestVectorConfigDefaults(t *testing.T) {
cfg, err := Default()
require.NoError(t, err)
assert.Equal(t, 8192, cfg.Vector.Embeddings.MaxInputChars)
assert.Empty(t, cfg.Vector.Embeddings.Servers)
assert.True(t, cfg.Vector.Embed.RunAfterSyncEnabled(),
"run_after_sync defaults to true when unset")
disabled := false
cfg.Vector.Embed.RunAfterSync = &disabled
assert.False(t, cfg.Vector.Embed.RunAfterSyncEnabled(),
"explicit false overrides the default")
assert.Equal(t, filepath.Join(cfg.DataDir, "vectors.db"),
cfg.Vector.ResolvedDBPath(cfg.DataDir), "falls back to <dataDir>/vectors.db")
cfg.Vector.DBPath = "/custom/path/vec.db"
assert.Equal(t, "/custom/path/vec.db", cfg.Vector.ResolvedDBPath(cfg.DataDir),
"explicit db_path overrides the fallback")
}
func TestVectorConfigAPIKeyEnv(t *testing.T) {
server := VectorEmbeddingsServerConfig{}
assert.Equal(t, "", server.APIKey(), "no env var configured")
server.APIKeyEnv = "AGENTSVIEW_TEST_VECTOR_API_KEY"
assert.Equal(t, "", server.APIKey(), "configured env var not set in environment")
t.Setenv("AGENTSVIEW_TEST_VECTOR_API_KEY", "secret-123")
assert.Equal(t, "secret-123", server.APIKey())
}
// minimalServers returns the smallest valid servers table for TOML load
// tests, as the raw map shape loadMinimalWithConfig marshals.
func minimalServers() map[string]any {
return map[string]any{
"local": map[string]any{
"endpoint": "http://localhost:11434/v1",
},
}
}
// TestVectorConfigTOMLLoad exercises the full config-file load path so the
// default-merge logic in applyConfigTOML (not just the section types in
// isolation) is covered, including per-server defaults and the ability to
// explicitly override a zero-value field like max_retries.
func TestVectorConfigTOMLLoad(t *testing.T) {
t.Run("unset server fields keep defaults, explicit zero overrides", func(t *testing.T) {
cfg := loadMinimalWithConfig(t, map[string]any{
"vector": map[string]any{
"enabled": true,
"embeddings": map[string]any{
"model": "nomic-embed-text",
"dimension": 768,
"servers": map[string]any{
"local": map[string]any{
"endpoint": "http://localhost:11434/v1",
"max_retries": 0,
},
},
},
},
})
require.True(t, cfg.Vector.Enabled)
server := cfg.Vector.Embeddings.Servers["local"]
assert.Equal(t, "http://localhost:11434/v1", server.Endpoint)
assert.Equal(t, 32, server.BatchSize, "unset batch_size keeps default")
assert.Equal(t, 4, server.Concurrency, "unset concurrency keeps default")
assert.Equal(t, "30s", server.Timeout, "unset timeout keeps default")
assert.Equal(t, 0, server.MaxRetries, "explicit max_retries=0 overrides default")
assert.Equal(t, 8192, cfg.Vector.Embeddings.MaxInputChars, "unset max_input_chars keeps default")
assert.Equal(t, "24h", cfg.Vector.Embed.BackstopInterval, "unset backstop_interval keeps default")
assert.False(t, cfg.Vector.IncludeAutomated, "unset include_automated keeps the false default")
assert.Empty(t, cfg.Vector.Embeddings.InputSuffix, "unset input_suffix defaults to empty")
assert.False(t, cfg.Vector.Embeddings.RequestDimensions,
"unset request_dimensions keeps the false default")
})
t.Run("request_dimensions true is loaded", func(t *testing.T) {
cfg := loadMinimalWithConfig(t, map[string]any{
"vector": map[string]any{
"enabled": true,
"embeddings": map[string]any{
"model": "qwen3-embedding:0.6b",
"dimension": 256,
"request_dimensions": true,
"servers": minimalServers(),
},
},
})
assert.True(t, cfg.Vector.Embeddings.RequestDimensions)
assert.Equal(t, 256, cfg.Vector.Embeddings.Dimension)
})
t.Run("named servers with default_server load and resolve", func(t *testing.T) {
cfg := loadMinimalWithConfig(t, map[string]any{
"vector": map[string]any{
"enabled": true,
"embeddings": map[string]any{
"model": "qwen3-embedding-4b",
"dimension": 2560,
"input_suffix": "<|endoftext|>",
"default_server": "local",
"servers": map[string]any{
"local": map[string]any{
"endpoint": "http://127.0.0.1:30000/v1",
},
"remote": map[string]any{
"endpoint": "http://build-box:30000/v1",
"timeout": "300s",
"concurrency": 6,
},
},
},
},
})
assert.Equal(t, "<|endoftext|>", cfg.Vector.Embeddings.InputSuffix)
assert.Equal(t, "local", cfg.Vector.Embeddings.DefaultServer)
name, server, err := cfg.Vector.Embeddings.Server("")
require.NoError(t, err)
assert.Equal(t, "local", name)
assert.Equal(t, "http://127.0.0.1:30000/v1", server.Endpoint)
_, remote, err := cfg.Vector.Embeddings.Server("remote")
require.NoError(t, err)
assert.Equal(t, "300s", remote.Timeout, "per-server timeout override")
assert.Equal(t, 6, remote.Concurrency, "per-server concurrency override")
assert.Equal(t, 32, remote.BatchSize, "unset per-server batch_size keeps default")
})
t.Run("include_automated true is loaded", func(t *testing.T) {
cfg := loadMinimalWithConfig(t, map[string]any{
"vector": map[string]any{
"enabled": true,
"include_automated": true,
"embeddings": map[string]any{
"model": "nomic-embed-text",
"dimension": 768,
"servers": minimalServers(),
},
},
})
assert.True(t, cfg.Vector.IncludeAutomated)
})
t.Run("enabled without servers fails to load", func(t *testing.T) {
err := loadMinimalErrWithConfig(t, map[string]any{
"vector": map[string]any{
"enabled": true,
"embeddings": map[string]any{
"model": "nomic-embed-text",
"dimension": 768,
},
},
})
require.Error(t, err)
assert.Contains(t, err.Error(), "at least one server")
})
t.Run("multiple servers without default_server fails to load", func(t *testing.T) {
servers := minimalServers()
servers["remote"] = map[string]any{"endpoint": "http://build-box:30000/v1"}
err := loadMinimalErrWithConfig(t, map[string]any{
"vector": map[string]any{
"enabled": true,
"embeddings": map[string]any{
"model": "nomic-embed-text",
"dimension": 768,
"servers": servers,
},
},
})
require.Error(t, err)
assert.Contains(t, err.Error(), "default_server is required")
})
t.Run("disabled section with no fields loads fine", func(t *testing.T) {
cfg := loadMinimalWithConfig(t, map[string]any{
"vector": map[string]any{},
})
assert.False(t, cfg.Vector.Enabled)
})
t.Run("explicit zero/negative operational overrides fail to load", func(t *testing.T) {
tests := []struct {
name string
server map[string]any
embed map[string]any
wantErr string
}{
{
name: "explicit zero batch_size",
server: map[string]any{"batch_size": 0},
wantErr: "batch_size",
},
{
name: "explicit negative batch_size",
server: map[string]any{"batch_size": -1},
wantErr: "batch_size",
},
{
name: "explicit zero concurrency",
server: map[string]any{"concurrency": 0},
wantErr: "concurrency",
},
{
name: "explicit negative max_retries",
server: map[string]any{"max_retries": -1},
wantErr: "max_retries",
},
{
name: "explicit zero timeout",
server: map[string]any{"timeout": "0s"},
wantErr: "timeout",
},
{
name: "explicit zero backstop_interval",
embed: map[string]any{"backstop_interval": "0s"},
wantErr: "use a negative value to disable",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
local := map[string]any{"endpoint": "http://localhost:11434/v1"}
maps.Copy(local, tt.server)
vector := map[string]any{
"enabled": true,
"embeddings": map[string]any{
"model": "nomic-embed-text",
"dimension": 768,
"servers": map[string]any{"local": local},
},
}
if tt.embed != nil {
vector["embed"] = tt.embed
}
err := loadMinimalErrWithConfig(t, map[string]any{"vector": vector})
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
})
}