All checks were successful
ci/woodpecker/tag/release Pipeline was successful
237 lines
6.6 KiB
Go
237 lines
6.6 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
renderformat "gitea.maximumdirect.net/eric/scriptorium/internal/format"
|
|
)
|
|
|
|
func TestLoadConfigMissingImplicitPathUsesBuiltInDefaults(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
missing := filepath.Join(tmp, "missing.yml")
|
|
|
|
got, err := LoadConfig(missing, false)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
want := BuiltInDefaults()
|
|
if got != want {
|
|
t.Fatalf("unexpected settings: got=%+v want=%+v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigMissingExplicitPathReturnsError(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
missing := filepath.Join(tmp, "missing.yml")
|
|
|
|
_, err := LoadConfig(missing, true)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing explicit config")
|
|
}
|
|
if !errors.Is(err, ErrConfigNotFound) {
|
|
t.Fatalf("expected ErrConfigNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigInvalidYAMLReturnsError(t *testing.T) {
|
|
path := writeConfigFile(t, "config.yml", "prompt_dir: [")
|
|
|
|
_, err := LoadConfig(path, true)
|
|
if err == nil {
|
|
t.Fatal("expected invalid YAML error")
|
|
}
|
|
if !errors.Is(err, ErrInvalidConfigYAML) {
|
|
t.Fatalf("expected ErrInvalidConfigYAML, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigInvalidYAMLImplicitPathReturnsError(t *testing.T) {
|
|
path := writeConfigFile(t, "config.yml", "prompt_dir: [")
|
|
|
|
_, err := LoadConfig(path, false)
|
|
if err == nil {
|
|
t.Fatal("expected invalid YAML error")
|
|
}
|
|
if !errors.Is(err, ErrInvalidConfigYAML) {
|
|
t.Fatalf("expected ErrInvalidConfigYAML, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigUnknownFieldReturnsError(t *testing.T) {
|
|
path := writeConfigFile(t, "config.yml", "unknown_field: true\n")
|
|
|
|
_, err := LoadConfig(path, true)
|
|
if err == nil {
|
|
t.Fatal("expected unknown field error")
|
|
}
|
|
if !errors.Is(err, ErrInvalidConfigYAML) {
|
|
t.Fatalf("expected ErrInvalidConfigYAML, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigAPIKeyFieldIsRejectedAsUnknown(t *testing.T) {
|
|
path := writeConfigFile(t, "config.yml", "api_key: secret\n")
|
|
|
|
_, err := LoadConfig(path, true)
|
|
if err == nil {
|
|
t.Fatal("expected unknown field error for api_key")
|
|
}
|
|
if !errors.Is(err, ErrInvalidConfigYAML) {
|
|
t.Fatalf("expected ErrInvalidConfigYAML, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigValidConfigSetsDirectoriesAndServerAddr(t *testing.T) {
|
|
path := writeConfigFile(t, "config.yml", `
|
|
prompt_dir: ./prompts
|
|
profile_dir: ./profiles
|
|
schema_dir: ./schemas
|
|
server:
|
|
addr: 127.0.0.1:9090
|
|
defaults:
|
|
render_format: json
|
|
`)
|
|
|
|
got, err := LoadConfig(path, true)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if got.PromptDir != filepath.Clean("./prompts") {
|
|
t.Fatalf("unexpected prompt_dir: %q", got.PromptDir)
|
|
}
|
|
if got.ProfileDir != filepath.Clean("./profiles") {
|
|
t.Fatalf("unexpected profile_dir: %q", got.ProfileDir)
|
|
}
|
|
if got.SchemaDir != filepath.Clean("./schemas") {
|
|
t.Fatalf("unexpected schema_dir: %q", got.SchemaDir)
|
|
}
|
|
if got.ServerAddr != "127.0.0.1:9090" {
|
|
t.Fatalf("unexpected server.addr: %q", got.ServerAddr)
|
|
}
|
|
if got.DefaultRenderFormat != renderformat.PreparedRunFormatJSON {
|
|
t.Fatalf("unexpected defaults.render_format: %q", got.DefaultRenderFormat)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigEmptyFileResolvesToBuiltInDefaults(t *testing.T) {
|
|
path := writeConfigFile(t, "config.yml", "")
|
|
|
|
got, err := LoadConfig(path, true)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
want := BuiltInDefaults()
|
|
if got != want {
|
|
t.Fatalf("unexpected settings: got=%+v want=%+v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigImplicitSearchPrefersUsrLocalEtcOverEtc(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
localPath := filepath.Join(tmp, "usr-local.yml")
|
|
etcPath := filepath.Join(tmp, "etc.yml")
|
|
|
|
if err := os.WriteFile(localPath, []byte("prompt_dir: ./from-usr-local\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(etcPath, []byte("prompt_dir: ./from-etc\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
orig := defaultConfigSearchPaths
|
|
defaultConfigSearchPaths = []string{localPath, etcPath}
|
|
t.Cleanup(func() { defaultConfigSearchPaths = orig })
|
|
|
|
got, err := LoadConfig("", false)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if got.PromptDir != filepath.Clean("./from-usr-local") {
|
|
t.Fatalf("expected usr-local config to win, got prompt_dir=%q", got.PromptDir)
|
|
}
|
|
}
|
|
|
|
func TestLoadConfigImplicitSearchFallsBackToEtcWhenUsrLocalMissing(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
missingLocal := filepath.Join(tmp, "missing-local.yml")
|
|
etcPath := filepath.Join(tmp, "etc.yml")
|
|
|
|
if err := os.WriteFile(etcPath, []byte("prompt_dir: ./from-etc\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
orig := defaultConfigSearchPaths
|
|
defaultConfigSearchPaths = []string{missingLocal, etcPath}
|
|
t.Cleanup(func() { defaultConfigSearchPaths = orig })
|
|
|
|
got, err := LoadConfig("", false)
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if got.PromptDir != filepath.Clean("./from-etc") {
|
|
t.Fatalf("expected etc fallback config, got prompt_dir=%q", got.PromptDir)
|
|
}
|
|
}
|
|
|
|
func TestApplyCLIOverridesAppliesPrecedence(t *testing.T) {
|
|
base := AppSettings{
|
|
PromptDir: "/from/config/prompts",
|
|
ProfileDir: "/from/config/profiles",
|
|
SchemaDir: "/from/config/schemas",
|
|
ServerAddr: ":1234",
|
|
DefaultRenderFormat: renderformat.PreparedRunFormatJSON,
|
|
}
|
|
|
|
got, err := ApplyCLIOverrides(base, CLIOverrides{
|
|
PromptDir: "./prompts-cli",
|
|
ProfileDir: "./profiles-cli",
|
|
SchemaDir: "./schemas-cli",
|
|
ServerAddr: ":8081",
|
|
RenderFormat: "text",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if got.PromptDir != filepath.Clean("./prompts-cli") {
|
|
t.Fatalf("unexpected prompt dir: %q", got.PromptDir)
|
|
}
|
|
if got.ProfileDir != filepath.Clean("./profiles-cli") {
|
|
t.Fatalf("unexpected profile dir: %q", got.ProfileDir)
|
|
}
|
|
if got.SchemaDir != filepath.Clean("./schemas-cli") {
|
|
t.Fatalf("unexpected schema dir: %q", got.SchemaDir)
|
|
}
|
|
if got.ServerAddr != ":8081" {
|
|
t.Fatalf("unexpected server addr: %q", got.ServerAddr)
|
|
}
|
|
if got.DefaultRenderFormat != renderformat.PreparedRunFormatText {
|
|
t.Fatalf("unexpected render format: %q", got.DefaultRenderFormat)
|
|
}
|
|
}
|
|
|
|
func TestApplyCLIOverridesInvalidRenderFormatReturnsError(t *testing.T) {
|
|
_, err := ApplyCLIOverrides(BuiltInDefaults(), CLIOverrides{RenderFormat: "yaml"})
|
|
if err == nil {
|
|
t.Fatal("expected invalid render format error")
|
|
}
|
|
if !errors.Is(err, ErrInvalidConfig) {
|
|
t.Fatalf("expected ErrInvalidConfig, got %v", err)
|
|
}
|
|
}
|
|
|
|
func writeConfigFile(t *testing.T, name, content string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), name)
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("failed to write config file: %v", err)
|
|
}
|
|
return path
|
|
}
|