240 lines
7.1 KiB
Go
240 lines
7.1 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
)
|
|
|
|
func TestPrecedenceFileValuesOverrideBuiltInDefaults(t *testing.T) {
|
|
cfg := applyFileConfig(t, `version: 4
|
|
concurrency:
|
|
total_llm: 4
|
|
stage_workers:
|
|
extract: 2
|
|
output:
|
|
directory: ./file-output
|
|
cache:
|
|
chunk_plans:
|
|
directory: ./file-plans
|
|
mode: refresh
|
|
checkpoints:
|
|
directory: ./file-checkpoints
|
|
debug:
|
|
directory: ./file-debug
|
|
`)
|
|
if cfg.Concurrency.TotalLLM != 4 || cfg.Concurrency.StageWorkers["extract"] != 2 ||
|
|
cfg.Output.Directory != "./file-output" || cfg.Cache.ChunkPlans.Directory != "file-plans" ||
|
|
cfg.Cache.ChunkPlans.Mode != pipeline.ChunkCacheRefresh || cfg.Cache.Checkpoints.Directory != "file-checkpoints" ||
|
|
cfg.Debug.Directory != "./file-debug" {
|
|
t.Fatalf("file values did not override defaults: %#v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestPrecedenceOperationalEnvironmentOverridesFileValues(t *testing.T) {
|
|
cfg := applyFileConfig(t, `version: 4
|
|
concurrency:
|
|
total_llm: 2
|
|
stage_workers:
|
|
extract: 1
|
|
output:
|
|
directory: ./file-output
|
|
cache:
|
|
chunk_plans:
|
|
directory: ./file-plans
|
|
mode: refresh
|
|
checkpoints:
|
|
directory: ./file-checkpoints
|
|
debug:
|
|
directory: ./file-debug
|
|
`)
|
|
env := map[string]string{
|
|
"NOTARIUS_TOTAL_LLM_CONCURRENCY": "8",
|
|
"NOTARIUS_STAGE_WORKERS_EXTRACT": "6",
|
|
"NOTARIUS_OUTPUT_DIR": "/env/output",
|
|
"NOTARIUS_CACHE_CHUNK_PLANS_MODE": "bypass",
|
|
"NOTARIUS_CACHE_CHUNK_PLANS_DIR": "/env/plans",
|
|
"NOTARIUS_CACHE_CHECKPOINTS_DIR": "/env/checkpoints",
|
|
"NOTARIUS_DEBUG_DIR": "/env/debug",
|
|
}
|
|
if err := cfg.ApplyEnvOverridesWithLookup(lookupValues(env)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Concurrency.TotalLLM != 8 || cfg.Concurrency.StageWorkers["extract"] != 6 ||
|
|
cfg.Output.Directory != "/env/output" || cfg.Cache.ChunkPlans.Directory != "/env/plans" ||
|
|
cfg.Cache.ChunkPlans.Mode != pipeline.ChunkCacheBypass || cfg.Cache.Checkpoints.Directory != "/env/checkpoints" ||
|
|
cfg.Debug.Directory != "/env/debug" {
|
|
t.Fatalf("environment values did not override file values: %#v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestPrecedenceExtractWorkersFollowEffectiveConcurrencyUnlessExplicit(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
file string
|
|
env map[string]string
|
|
wantTotal int
|
|
wantWorker int
|
|
}{
|
|
{
|
|
name: "default follows environment total",
|
|
file: "version: 4\n",
|
|
env: map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "5"},
|
|
wantTotal: 5,
|
|
wantWorker: 5,
|
|
},
|
|
{
|
|
name: "file worker is retained",
|
|
file: "version: 4\nconcurrency:\n total_llm: 3\n stage_workers:\n extract: 2\n",
|
|
env: map[string]string{"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6"},
|
|
wantTotal: 6,
|
|
wantWorker: 2,
|
|
},
|
|
{
|
|
name: "environment worker is retained",
|
|
file: "version: 4\nconcurrency:\n total_llm: 2\n",
|
|
env: map[string]string{
|
|
"NOTARIUS_TOTAL_LLM_CONCURRENCY": "6",
|
|
"NOTARIUS_STAGE_WORKERS_EXTRACT": "4",
|
|
},
|
|
wantTotal: 6,
|
|
wantWorker: 4,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := applyFileConfig(t, tt.file)
|
|
if err := cfg.ApplyEnvOverridesWithLookup(lookupValues(tt.env)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Concurrency.TotalLLM != tt.wantTotal || cfg.Concurrency.StageWorkers["extract"] != tt.wantWorker {
|
|
t.Fatalf("concurrency = %#v, want total %d and extract %d", cfg.Concurrency, tt.wantTotal, tt.wantWorker)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrecedenceEmptyFileCacheDirectoriesDeferPerUserResolution(t *testing.T) {
|
|
cfg := applyFileConfig(t, `version: 4
|
|
cache:
|
|
chunk_plans:
|
|
directory: ""
|
|
checkpoints:
|
|
directory: ""
|
|
`)
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Fatalf("empty file cache directories should be valid: %v", err)
|
|
}
|
|
if cfg.Cache.ChunkPlans.Directory != "" || cfg.Cache.Checkpoints.Directory != "" {
|
|
t.Fatalf("empty cache directories were not preserved for deferred resolution: %#v", cfg.Cache)
|
|
}
|
|
resolver := func() (string, error) { return "/user/cache", nil }
|
|
chunkPlans, err := DefaultChunkPlanRoot(resolver)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
checkpoints, err := DefaultCheckpointRoot(resolver)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if chunkPlans != "/user/cache/notarius/chunk-plans" || checkpoints != "/user/cache/notarius/checkpoints" {
|
|
t.Fatalf("deferred cache roots = %q, %q", chunkPlans, checkpoints)
|
|
}
|
|
}
|
|
|
|
func TestDefaultCacheRootsRejectInvalidUserCacheResolvers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
resolver func() (string, error)
|
|
want string
|
|
}{
|
|
{name: "nil resolver", want: "must not be nil"},
|
|
{
|
|
name: "resolver failure",
|
|
resolver: func() (string, error) {
|
|
return "", errors.New("cache home unavailable")
|
|
},
|
|
want: "resolve user cache directory",
|
|
},
|
|
{name: "empty directory", resolver: func() (string, error) { return " ", nil }, want: "must not be empty"},
|
|
}
|
|
families := []struct {
|
|
name string
|
|
root func(func() (string, error)) (string, error)
|
|
}{
|
|
{name: "chunk plans", root: DefaultChunkPlanRoot},
|
|
{name: "checkpoints", root: DefaultCheckpointRoot},
|
|
}
|
|
|
|
for _, family := range families {
|
|
for _, tt := range tests {
|
|
t.Run(family.name+"/"+tt.name, func(t *testing.T) {
|
|
_, err := family.root(tt.resolver)
|
|
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
|
t.Fatalf("error = %v, want substring %q", err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEnvEmptyDirectoryOverridesAreErrors(t *testing.T) {
|
|
tests := []string{
|
|
"NOTARIUS_OUTPUT_DIR",
|
|
"NOTARIUS_CACHE_CHUNK_PLANS_DIR",
|
|
"NOTARIUS_CACHE_CHECKPOINTS_DIR",
|
|
"NOTARIUS_DEBUG_DIR",
|
|
}
|
|
for _, name := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
cfg := Default()
|
|
err := cfg.ApplyEnvOverridesWithLookup(lookupValues(map[string]string{name: " \t"}))
|
|
if err == nil || !strings.Contains(err.Error(), name) {
|
|
t.Fatalf("error = %v, want responsible environment variable", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnvInvalidIntegersAndChunkCacheModesReportTheirNames(t *testing.T) {
|
|
tests := map[string]string{
|
|
"NOTARIUS_TOTAL_LLM_CONCURRENCY": "not-an-integer",
|
|
"NOTARIUS_STAGE_WORKERS_EXTRACT": "not-an-integer",
|
|
"NOTARIUS_CACHE_CHUNK_PLANS_MODE": "not-a-cache-mode",
|
|
}
|
|
for name, value := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
cfg := Default()
|
|
err := cfg.ApplyEnvOverridesWithLookup(lookupValues(map[string]string{name: value}))
|
|
if err == nil || !strings.Contains(err.Error(), name) {
|
|
t.Fatalf("error = %v, want responsible environment variable", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnvRemovedProviderVariablesAreIgnored(t *testing.T) {
|
|
before := Default()
|
|
cfg := Default()
|
|
removed := map[string]string{
|
|
"NOTARIUS_LLM_DEFAULT_ENDPOINT": "ignored-provider-setting",
|
|
"NOTARIUS_LLM_DEFAULT_MODEL": "ignored-provider-setting",
|
|
}
|
|
if err := cfg.ApplyEnvOverridesWithLookup(lookupValues(removed)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(cfg, before) {
|
|
t.Fatalf("removed provider variables changed configuration: %#v", cfg)
|
|
}
|
|
}
|
|
|
|
func lookupValues(values map[string]string) func(string) (string, bool) {
|
|
return func(name string) (string, bool) {
|
|
value, ok := values[name]
|
|
return value, ok
|
|
}
|
|
}
|