- fix: The AccessTTL and RefreshTTL fields in JWTConfig now use time.Duration type directly instead of string with ParseDuration methods. The config validation now checks for positive durations rather than parsing strings.
213 lines
4.8 KiB
Go
213 lines
4.8 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDefaults(t *testing.T) {
|
|
v := New()
|
|
cfg, err := Load(v, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
got any
|
|
want any
|
|
}{
|
|
{"server.host", cfg.Server.Host, "0.0.0.0"},
|
|
{"server.port", cfg.Server.Port, 10086},
|
|
{"database.driver", cfg.Database.Driver, "sqlite3"},
|
|
{"database.sqlite.path", cfg.Database.SQLite.Path, "data/mygo.db"},
|
|
{"storage.driver", cfg.Storage.Driver, "local"},
|
|
{"storage.local.path", cfg.Storage.Local.Path, "data/files"},
|
|
{"jwt.access_ttl", cfg.JWT.AccessTTL, 15 * time.Minute},
|
|
{"jwt.refresh_ttl", cfg.JWT.RefreshTTL, 168 * time.Hour},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.got != tt.want {
|
|
t.Errorf("got %v, want %v", tt.got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFromYAML(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
|
|
yaml := `
|
|
server:
|
|
host: 127.0.0.1
|
|
port: 9090
|
|
|
|
database:
|
|
driver: sqlite3
|
|
sqlite:
|
|
path: /tmp/mygo.db
|
|
|
|
storage:
|
|
driver: local
|
|
local:
|
|
path: /tmp/mygo-storage
|
|
|
|
jwt:
|
|
secret: test-secret
|
|
access_ttl: 30m
|
|
refresh_ttl: 72h
|
|
`
|
|
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
v := New()
|
|
cfg, err := Load(v, path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if cfg.Server.Host != "127.0.0.1" {
|
|
t.Errorf("server.host = %q, want %q", cfg.Server.Host, "127.0.0.1")
|
|
}
|
|
if cfg.Server.Port != 9090 {
|
|
t.Errorf("server.port = %d, want %d", cfg.Server.Port, 9090)
|
|
}
|
|
if cfg.Database.SQLite.Path != "/tmp/mygo.db" {
|
|
t.Errorf("database.sqlite.path = %q, want %q", cfg.Database.SQLite.Path, "/tmp/mygo.db")
|
|
}
|
|
if cfg.Storage.Local.Path != "/tmp/mygo-storage" {
|
|
t.Errorf("storage.local.path = %q, want %q", cfg.Storage.Local.Path, "/tmp/mygo-storage")
|
|
}
|
|
if cfg.JWT.Secret != "test-secret" {
|
|
t.Errorf("jwt.secret = %q, want %q", cfg.JWT.Secret, "test-secret")
|
|
}
|
|
if cfg.JWT.AccessTTL != 30*time.Minute {
|
|
t.Errorf("jwt.access_ttl = %v, want %v", cfg.JWT.AccessTTL, 30*time.Minute)
|
|
}
|
|
if cfg.JWT.RefreshTTL != 72*time.Hour {
|
|
t.Errorf("jwt.refresh_ttl = %v, want %v", cfg.JWT.RefreshTTL, 72*time.Hour)
|
|
}
|
|
}
|
|
|
|
func TestEnvOverride(t *testing.T) {
|
|
t.Setenv("MYGO_SERVER_PORT", "8080")
|
|
t.Setenv("MYGO_SERVER_HOST", "192.168.1.1")
|
|
t.Setenv("MYGO_JWT_SECRET", "env-secret")
|
|
t.Setenv("MYGO_DATABASE_SQLITE_PATH", "/env/path/db.sqlite")
|
|
|
|
v := New()
|
|
cfg, err := Load(v, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if cfg.Server.Port != 8080 {
|
|
t.Errorf("server.port = %d, want %d", cfg.Server.Port, 8080)
|
|
}
|
|
if cfg.Server.Host != "192.168.1.1" {
|
|
t.Errorf("server.host = %q, want %q", cfg.Server.Host, "192.168.1.1")
|
|
}
|
|
if cfg.JWT.Secret != "env-secret" {
|
|
t.Errorf("jwt.secret = %q, want %q", cfg.JWT.Secret, "env-secret")
|
|
}
|
|
if cfg.Database.SQLite.Path != "/env/path/db.sqlite" {
|
|
t.Errorf("database.sqlite.path = %q, want %q", cfg.Database.SQLite.Path, "/env/path/db.sqlite")
|
|
}
|
|
}
|
|
|
|
func TestEnvOverridesYAML(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
|
|
yaml := `
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 10086
|
|
`
|
|
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Setenv("MYGO_SERVER_PORT", "9999")
|
|
|
|
v := New()
|
|
cfg, err := Load(v, path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if cfg.Server.Port != 9999 {
|
|
t.Errorf("server.port = %d, want %d (env should override YAML)", cfg.Server.Port, 9999)
|
|
}
|
|
if cfg.Server.Host != "0.0.0.0" {
|
|
t.Errorf("server.host = %q, want %q (YAML should be used when env is absent)", cfg.Server.Host, "0.0.0.0")
|
|
}
|
|
}
|
|
|
|
func TestValidatePortRange(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
|
|
yaml := `
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 0
|
|
`
|
|
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
v := New()
|
|
_, err := Load(v, path)
|
|
if err == nil {
|
|
t.Fatal("expected error for port 0, got nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateEmptySecret(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
|
|
yaml := `
|
|
jwt:
|
|
secret: ""
|
|
`
|
|
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
v := New()
|
|
_, err := Load(v, path)
|
|
if err == nil {
|
|
t.Fatal("expected error for empty jwt.secret, got nil")
|
|
}
|
|
}
|
|
|
|
func TestExplicitConfigFileNotFound(t *testing.T) {
|
|
v := New()
|
|
_, err := Load(v, "/nonexistent/path/config.yaml")
|
|
if err == nil {
|
|
t.Fatal("expected error when explicitly specifying a nonexistent config file")
|
|
}
|
|
}
|
|
|
|
func TestJWTConfigAccessDuration(t *testing.T) {
|
|
j := JWTConfig{AccessTTL: 15 * time.Minute}
|
|
if j.AccessTTL != 15*time.Minute {
|
|
t.Errorf("AccessTTL = %v, want %v", j.AccessTTL, 15*time.Minute)
|
|
}
|
|
}
|
|
|
|
func TestJWTConfigRefreshDuration(t *testing.T) {
|
|
j := JWTConfig{RefreshTTL: 168 * time.Hour}
|
|
if j.RefreshTTL != 168*time.Hour {
|
|
t.Errorf("RefreshTTL = %v, want %v", j.RefreshTTL, 168*time.Hour)
|
|
}
|
|
}
|