148 lines
4.6 KiB
Go
148 lines
4.6 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func writeConfig(t *testing.T, path, body string) {
|
|
t.Helper()
|
|
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestLoadDirectoryAndDurations(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
writeConfig(t, path, "server:\n http:\n addr: :9000\n timeout: 600s\ndata:\n redis:\n read_timeout: 200ms\nadmin:\n jwt:\n expires_time: 168h\n media:\n session_ttl: 36\n")
|
|
loaded, err := Load(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if loaded.Server == nil || loaded.Server.HTTP == nil || loaded.Server.HTTP.Timeout != 10*time.Minute {
|
|
t.Fatalf("server timeout = %#v", loaded.Server)
|
|
}
|
|
if loaded.Data == nil || loaded.Data.Redis == nil || loaded.Data.Redis.ReadTimeout != 200*time.Millisecond {
|
|
t.Fatalf("redis timeout = %#v", loaded.Data)
|
|
}
|
|
if loaded.Admin == nil || loaded.Admin.JWT == nil || loaded.Admin.JWT.ExpiresTime != 7*24*time.Hour {
|
|
t.Fatalf("jwt duration = %#v", loaded.Admin)
|
|
}
|
|
if loaded.Admin.ConfigPath != path {
|
|
t.Fatalf("config path = %q, want %q", loaded.Admin.ConfigPath, path)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsInvalidDuration(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
writeConfig(t, path, "server:\n http:\n timeout: definitely-not-a-duration\n")
|
|
if _, err := Load(path); err == nil {
|
|
t.Fatal("invalid duration was accepted")
|
|
}
|
|
}
|
|
|
|
func TestLoadEnvironmentOverride(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
writeConfig(t, path, "server:\n http:\n addr: :8000\n")
|
|
t.Setenv("KRA_SERVER_HTTP_ADDR", ":9100")
|
|
loaded, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if loaded.Server == nil || loaded.Server.HTTP == nil || loaded.Server.HTTP.Addr != ":9100" {
|
|
t.Fatalf("environment override = %#v", loaded.Server)
|
|
}
|
|
}
|
|
|
|
func TestStoreSnapshotIsolationAndSubscriptions(t *testing.T) {
|
|
store := NewStore(&Config{Admin: &Admin{JWT: &JWT{SigningKey: "before"}}})
|
|
var calls atomic.Int32
|
|
stop := store.Subscribe(func(value *Config) {
|
|
calls.Add(1)
|
|
value.Admin.JWT.SigningKey = "callback-mutation"
|
|
})
|
|
store.Replace(&Config{Admin: &Admin{JWT: &JWT{SigningKey: "after"}}})
|
|
stop()
|
|
stop()
|
|
value := store.Snapshot()
|
|
if value == nil || value.Admin == nil || value.Admin.JWT == nil || value.Admin.JWT.SigningKey != "after" {
|
|
t.Fatalf("snapshot was mutated by callback: %#v", value)
|
|
}
|
|
if calls.Load() != 1 {
|
|
t.Fatalf("callback count = %d", calls.Load())
|
|
}
|
|
}
|
|
|
|
func TestStoreConcurrentReadersAndReplacements(t *testing.T) {
|
|
store := NewStore(&Config{Data: &Data{Database: &Database{Name: "initial"}}})
|
|
var wait sync.WaitGroup
|
|
for i := 0; i < 8; i++ {
|
|
wait.Add(1)
|
|
go func() {
|
|
defer wait.Done()
|
|
for j := 0; j < 500; j++ {
|
|
_ = store.Snapshot()
|
|
}
|
|
}()
|
|
}
|
|
for i := 0; i < 500; i++ {
|
|
store.Replace(&Config{Data: &Data{Database: &Database{Name: "next"}}})
|
|
}
|
|
wait.Wait()
|
|
}
|
|
|
|
func TestStoreWatchReloadAndClose(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
writeConfig(t, path, "admin:\n app:\n app_id: first\n")
|
|
store, err := LoadStore(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
writeConfig(t, path, "admin:\n app:\n app_id: second\n")
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
value := store.Snapshot()
|
|
if value != nil && value.Admin != nil && value.Admin.App != nil && value.Admin.App.AppID == "second" {
|
|
return
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatalf("watcher did not reload configuration: %#v", store.Snapshot())
|
|
}
|
|
|
|
func TestStoreWatchPreservesDatabaseBackedIntegrationSettings(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
writeConfig(t, path, "data: {}\nadmin: {}\n")
|
|
store, err := LoadStore(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer store.Close()
|
|
current := store.Snapshot()
|
|
current.Admin.Storage = &Storage{Type: "local"}
|
|
current.Admin.Email = &Email{Host: "smtp.example.com"}
|
|
store.Replace(current)
|
|
|
|
writeConfig(t, path, "data:\n redis:\n addr: 127.0.0.1:6379\nadmin: {}\n")
|
|
deadline := time.Now().Add(3 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
value := store.Snapshot()
|
|
if value != nil && value.Data != nil && value.Data.Redis != nil && value.Data.Redis.Addr != "" {
|
|
if value.Admin == nil || value.Admin.Storage == nil || value.Admin.Storage.Type != "local" || value.Admin.Email == nil || value.Admin.Email.Host != "smtp.example.com" {
|
|
t.Fatalf("database-backed settings were lost: %#v", value.Admin)
|
|
}
|
|
return
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
t.Fatal("watcher did not publish the changed file")
|
|
}
|