Files
mygo/internal/config/load_test.go
Huxley 901a769ee7 Complete foundational data layer with repository implementation
- Add GORM dependencies for SQLite and PostgreSQL
- Create domain models (User, Session, File) with common errors
- Implement repository interfaces and database layer with migrations
- Update WebApp to bootstrap with database and repositories
- Add comprehensive unit tests for repository methods
- Update config structure to support multiple database drivers
- Extend AGENTS.md with debugging principles and dependency rules
2026-04-28 13:32:33 +08:00

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, "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
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 != "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_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: "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)
}
}