package config_test
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"sourcecraft.dev/bigbes/lethe/internal/config"
)
// validForwardAuthYAML is a baseline valid configuration with forward-auth
// enabled. Tests typically clone and tweak this string for negative cases.
const validForwardAuthYAML = `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice", "bob"]
admins: ["alice"]
forward_auth:
enabled: true
oidc:
enabled: false
logging:
level: "info"
format: "tint"
ingest: {}
`
const validOIDCYAML = `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: false
oidc:
enabled: true
issuer: "https://auth.example.com"
audience: "lethe"
logging:
level: "info"
format: "tint"
ingest: {}
`
const validBothEnabledYAML = `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: true
oidc:
enabled: true
issuer: "https://auth.example.com"
audience: "lethe"
logging:
level: "info"
format: "tint"
ingest: {}
`
func writeYAML(t *testing.T, body string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "config.yaml")
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatalf("write yaml: %v", err)
}
return path
}
func TestLoad_ValidForwardAuth(t *testing.T) {
path := writeYAML(t, validForwardAuthYAML)
cfg, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Server.Bind != "127.0.0.1:8080" {
t.Errorf("Server.Bind = %q", cfg.Server.Bind)
}
if !cfg.Auth.ForwardAuth.Enabled {
t.Error("ForwardAuth.Enabled = false, want true")
}
}
func TestLoad_ValidOIDC(t *testing.T) {
path := writeYAML(t, validOIDCYAML)
cfg, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if !cfg.Auth.OIDC.Enabled {
t.Error("OIDC.Enabled = false, want true")
}
if cfg.Auth.OIDC.Issuer != "https://auth.example.com" {
t.Errorf("OIDC.Issuer = %q", cfg.Auth.OIDC.Issuer)
}
}
func TestLoad_ValidBothEnabled(t *testing.T) {
path := writeYAML(t, validBothEnabledYAML)
if _, err := config.Load(path); err != nil {
t.Fatalf("Load: %v", err)
}
}
func TestLoad_Defaults(t *testing.T) {
path := writeYAML(t, validForwardAuthYAML)
cfg, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Auth.ForwardAuth.UserHeader != "Remote-User" {
t.Errorf("ForwardAuth.UserHeader = %q, want Remote-User", cfg.Auth.ForwardAuth.UserHeader)
}
if cfg.Auth.OIDC.UsernameClaim != "preferred_username" {
t.Errorf("OIDC.UsernameClaim = %q, want preferred_username", cfg.Auth.OIDC.UsernameClaim)
}
if cfg.Ingest.MaxBodyBytes != 16*1024*1024 {
t.Errorf("Ingest.MaxBodyBytes = %d, want %d", cfg.Ingest.MaxBodyBytes, 16*1024*1024)
}
if cfg.Ingest.MaxTurnContentBytes != 4*1024*1024 {
t.Errorf("Ingest.MaxTurnContentBytes = %d, want %d", cfg.Ingest.MaxTurnContentBytes, 4*1024*1024)
}
if cfg.Ingest.ChunkSize != 500 {
t.Errorf("Ingest.ChunkSize = %d, want 500", cfg.Ingest.ChunkSize)
}
if cfg.Database.BusyTimeout != 5*time.Second {
t.Errorf("Database.BusyTimeout = %s, want 5s", cfg.Database.BusyTimeout)
}
if cfg.Server.ShutdownGrace != 10*time.Second {
t.Errorf("Server.ShutdownGrace = %s, want 10s", cfg.Server.ShutdownGrace)
}
}
func TestLoad_EmptyAllowlistRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: []
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error for empty allowed_users, got nil")
}
}
func TestLoad_NonLoopbackBindRejected(t *testing.T) {
body := strings.Replace(validForwardAuthYAML, `"127.0.0.1:8080"`, `"0.0.0.0:8080"`, 1)
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error for non-loopback bind, got nil")
}
}
func TestLoad_BothAuthModesDisabledRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: false
oidc:
enabled: false
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error when both auth modes are disabled, got nil")
}
}
func TestLoad_OIDCEnabledWithoutIssuerRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: false
oidc:
enabled: true
audience: "lethe"
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error for OIDC enabled without issuer, got nil")
}
}
func TestLoad_OIDCEnabledWithNonURLIssuerRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: false
oidc:
enabled: true
issuer: "not a url"
audience: "lethe"
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error for non-URL issuer, got nil")
}
}
func TestLoad_AdminNotInAllowedUsersRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
admins: ["mallory"]
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error when admin is not in allowed_users, got nil")
}
}
func TestLoad_EmptyAdminsAccepted(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
admins: []
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err != nil {
t.Fatalf("expected empty admins to be accepted, got %v", err)
}
}
func TestLoad_MissingDatabasePathRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database: {}
auth:
allowed_users: ["alice"]
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error for missing database.path, got nil")
}
}
func TestLoad_MaxTurnContentBytesGreaterThanMaxBodyBytesRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest:
max_body_bytes: 1024
max_turn_content_bytes: 4096
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error when max_turn_content_bytes > max_body_bytes, got nil")
}
}
func TestLoad_UnknownYAMLKeyRejected(t *testing.T) {
body := `
server:
bind: "127.0.0.1:8080"
totally_unknown_key: 42
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest: {}
`
if _, err := config.Load(writeYAML(t, body)); err == nil {
t.Fatal("expected error for unknown YAML key, got nil")
}
}
func TestLoad_EnvOverride(t *testing.T) {
// Use a YAML body without admins so the override test only exercises
// the slice-decoding path (LETHE_AUTH_ALLOWED_USERS overrides YAML).
body := `
server:
bind: "127.0.0.1:8080"
database:
path: "./lethe.db"
auth:
allowed_users: ["alice"]
forward_auth:
enabled: true
logging:
level: "info"
format: "tint"
ingest: {}
`
path := writeYAML(t, body)
t.Setenv("LETHE_AUTH_ALLOWED_USERS", "carol,dave")
cfg, err := config.Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
got := cfg.Auth.AllowedUsers
if len(got) != 2 || got[0] != "carol" || got[1] != "dave" {
t.Errorf("AllowedUsers = %v, want [carol dave]", got)
}
}
func TestMustLoad_PanicsOnError(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expected MustLoad to panic on missing file")
}
}()
config.MustLoad("/nonexistent/path/that/should/not/exist.yaml")
}