- 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
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, "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)
|
|
}
|
|
}
|