- The mapstructure library is no longer needed for direct duration parsing since we now store TTLs as string durations (e.g., "15m", "168h") and parse them on demand via helper methods. - This allows more flexible duration formats in configuration and moves the parsing responsibility to the JWT config struct itself.
212 lines
4.7 KiB
Go
212 lines
4.7 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.path", cfg.Database.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, "15m"},
|
|
{"jwt.refresh_ttl", cfg.JWT.RefreshTTL, "168h"},
|
|
}
|
|
|
|
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
|
|
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.Path != "/tmp/mygo.db" {
|
|
t.Errorf("database.path = %q, want %q", cfg.Database.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 != "30m" {
|
|
t.Errorf("jwt.access_ttl = %q, want %q", cfg.JWT.AccessTTL, "30m")
|
|
}
|
|
if cfg.JWT.RefreshTTL != "72h" {
|
|
t.Errorf("jwt.refresh_ttl = %q, want %q", cfg.JWT.RefreshTTL, "72h")
|
|
}
|
|
}
|
|
|
|
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_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.Path != "/env/path/db.sqlite" {
|
|
t.Errorf("database.path = %q, want %q", cfg.Database.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: "15m"}
|
|
if got := j.AccessDuration(); got != 15*time.Minute {
|
|
t.Errorf("AccessDuration() = %v, want %v", got, 15*time.Minute)
|
|
}
|
|
}
|
|
|
|
func TestJWTConfigRefreshDuration(t *testing.T) {
|
|
j := JWTConfig{RefreshTTL: "168h"}
|
|
if got := j.RefreshDuration(); got != 168*time.Hour {
|
|
t.Errorf("RefreshDuration() = %v, want %v", got, 168*time.Hour)
|
|
}
|
|
}
|