Files
summercms/compass/config_test.go
Jakub Zych 255de605b1 test(01-02): add failing test for layered compass config
- Cover six-level precedence, env mapping, .env, Set/Persist/Reload
- Require HasConfig merge before plugin Register
- Assert hello command observes overlay and plugin defaults
2026-09-16 13:13:02 +02:00

244 lines
6.3 KiB
Go

package compass
import (
"os"
"path/filepath"
"testing"
"testing/fstest"
)
func TestLoadStringFromAppSection(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "app.yaml")
if err := os.WriteFile(path, []byte("name: hello-app\n"), 0o644); err != nil {
t.Fatal(err)
}
cfg, err := Load(dir)
if err != nil {
t.Fatalf("Load: %v", err)
}
if got := cfg.String("app.name"); got != "hello-app" {
t.Fatalf("String(app.name) = %q, want hello-app", got)
}
}
func TestOpenDefaultsEnvToProduction(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"app.yaml": "name: base\ndebug: false\n",
"env/production/app.yaml": "debug: true\n",
"env/development/app.yaml": "name: dev\n",
})
cfg, err := Open(Options{Dir: dir, Environ: []string{}})
if err != nil {
t.Fatalf("Open: %v", err)
}
if cfg.Environment() != "production" {
t.Fatalf("Environment = %q, want production", cfg.Environment())
}
if got := cfg.String("app.name"); got != "base" {
t.Fatalf("name = %q, want base", got)
}
if !cfg.Bool("app.debug") {
t.Fatal("production overlay should set app.debug")
}
}
func TestExplicitEnvWinsOverSUMMER_ENV(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"app.yaml": "name: base\n",
"env/development/app.yaml": "name: from-dev\n",
"env/staging/app.yaml": "name: from-staging\n",
})
cfg, err := Open(Options{
Dir: dir,
Env: "staging",
Environ: []string{"SUMMER_ENV=development"},
})
if err != nil {
t.Fatalf("Open: %v", err)
}
if cfg.Environment() != "staging" {
t.Fatalf("Environment = %q, want staging", cfg.Environment())
}
if got := cfg.String("app.name"); got != "from-staging" {
t.Fatalf("name = %q, want from-staging", got)
}
}
func TestDeepMergeKeepsUntouchedNestedKeys(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"database.yaml": `
host: localhost
port: 5432
pool:
max: 10
idle: 2
`,
"env/development/database.yaml": `
host: dev-db
pool:
max: 20
`,
})
cfg, err := Open(Options{Dir: dir, Env: "development", Environ: []string{}})
if err != nil {
t.Fatalf("Open: %v", err)
}
if got := cfg.String("database.host"); got != "dev-db" {
t.Fatalf("host = %q, want dev-db", got)
}
if got := cfg.Int("database.port"); got != 5432 {
t.Fatalf("port = %d, want 5432", got)
}
if got := cfg.Int("database.pool.max"); got != 20 {
t.Fatalf("pool.max = %d, want 20", got)
}
if got := cfg.Int("database.pool.idle"); got != 2 {
t.Fatalf("pool.idle = %d, want 2", got)
}
}
func TestMergePluginConfigAtBarePluginID(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"app.yaml": "name: hello-app\n",
})
cfg, err := Open(Options{Dir: dir, Environ: []string{}})
if err != nil {
t.Fatalf("Open: %v", err)
}
fsys := fstest.MapFS{
"config/config.yaml": {Data: []byte("posts_per_page: 10\nshow_author: true\n")},
}
if err := cfg.MergePlugin("golem15.hello", fsys); err != nil {
t.Fatalf("MergePlugin: %v", err)
}
if got := cfg.Int("golem15.hello.posts_per_page"); got != 10 {
t.Fatalf("posts_per_page = %d, want 10", got)
}
if !cfg.Bool("golem15.hello.show_author") {
t.Fatal("show_author should be true")
}
if !cfg.Has("golem15.hello.posts_per_page") {
t.Fatal("Has(golem15.hello.posts_per_page) = false")
}
got, ok := cfg.Lookup("golem15.hello.posts_per_page")
if !ok {
t.Fatal("Lookup missing posts_per_page")
}
switch v := got.(type) {
case int:
if v != 10 {
t.Fatalf("Lookup = %v, want 10", got)
}
case int64:
if v != 10 {
t.Fatalf("Lookup = %v, want 10", got)
}
case uint64:
if v != 10 {
t.Fatalf("Lookup = %v, want 10", got)
}
default:
// YAML may decode as int or the getter path may return the raw value.
if cfg.Int("golem15.hello.posts_per_page") != 10 {
t.Fatalf("Lookup type %T value %v", got, got)
}
}
var section struct {
PostsPerPage int `koanf:"posts_per_page"`
ShowAuthor bool `koanf:"show_author"`
}
if err := cfg.LoadSection("golem15.hello", &section); err != nil {
t.Fatalf("LoadSection: %v", err)
}
if section.PostsPerPage != 10 || !section.ShowAuthor {
t.Fatalf("LoadSection = %+v", section)
}
}
func TestEnvVarsOverridePluginDefaults(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"app.yaml": "name: hello-app\n",
})
cfg, err := Open(Options{
Dir: dir,
Environ: []string{
"SUMMER_GOLEM15__HELLO__POSTS_PER_PAGE=25",
},
})
if err != nil {
t.Fatalf("Open: %v", err)
}
fsys := fstest.MapFS{
"config/config.yaml": {Data: []byte("posts_per_page: 10\n")},
}
if err := cfg.MergePlugin("golem15.hello", fsys); err != nil {
t.Fatalf("MergePlugin: %v", err)
}
if got := cfg.Int("golem15.hello.posts_per_page"); got != 25 {
t.Fatalf("posts_per_page = %d, want 25 (env over plugin default)", got)
}
}
func TestPriorityRuntimeSetThenPersistThenEnvThenOverlayThenBaseThenPlugin(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"app.yaml": "name: base\n",
"env/development/app.yaml": "name: overlay\n",
"env/development/overrides.yaml": "app:\n name: persisted\n",
})
cfg, err := Open(Options{
Dir: dir,
Env: "development",
Environ: []string{"SUMMER_APP__NAME=from-env"},
})
if err != nil {
t.Fatalf("Open: %v", err)
}
if got := cfg.String("app.name"); got != "persisted" {
t.Fatalf("before Set, name = %q, want persisted", got)
}
if err := cfg.Set("app.name", "runtime"); err != nil {
t.Fatalf("Set: %v", err)
}
if got := cfg.String("app.name"); got != "runtime" {
t.Fatalf("after Set, name = %q, want runtime", got)
}
}
func TestSortedSectionFilesLoadDeterministically(t *testing.T) {
dir := writeConfigTree(t, map[string]string{
"b.yaml": "value: b\n",
"a.yaml": "value: a\nshared: 1\n",
})
cfg, err := Open(Options{Dir: dir, Environ: []string{}})
if err != nil {
t.Fatalf("Open: %v", err)
}
if got := cfg.String("a.value"); got != "a" {
t.Fatalf("a.value = %q, want a", got)
}
if got := cfg.String("b.value"); got != "b" {
t.Fatalf("b.value = %q, want b", got)
}
if got := cfg.Int("a.shared"); got != 1 {
t.Fatalf("a.shared = %d, want 1", got)
}
}
func writeConfigTree(t *testing.T, files map[string]string) string {
t.Helper()
dir := t.TempDir()
for name, body := range files {
path := filepath.Join(dir, name)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
return dir
}