Files
mc-proxy/internal/config/config_test.go
Kyle Isom 9cba3241e8 Add SQLite persistence and write-through gRPC mutations
Database (internal/db) stores listeners, routes, and firewall rules with
WAL mode, foreign keys, and idempotent migrations. First run seeds from
TOML config; subsequent runs load from DB as source of truth.

gRPC admin API now writes to the database before updating in-memory state
(write-through cache pattern). Adds snapshot command for VACUUM INTO
backups. Refactors firewall.New to accept raw rule slices instead of
config struct for flexibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 03:07:30 -07:00

207 lines
4.2 KiB
Go

package config
import (
"os"
"path/filepath"
"testing"
)
func TestLoadValid(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.toml")
data := `
[database]
path = "/tmp/test.db"
[[listeners]]
addr = ":443"
[[listeners.routes]]
hostname = "example.com"
backend = "127.0.0.1:8443"
[proxy]
connect_timeout = "5s"
idle_timeout = "300s"
shutdown_timeout = "30s"
[log]
level = "info"
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err)
}
cfg, err := Load(path)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg.Listeners) != 1 {
t.Fatalf("got %d listeners, want 1", len(cfg.Listeners))
}
if cfg.Listeners[0].Addr != ":443" {
t.Fatalf("got listener addr %q, want %q", cfg.Listeners[0].Addr, ":443")
}
if len(cfg.Listeners[0].Routes) != 1 {
t.Fatalf("got %d routes, want 1", len(cfg.Listeners[0].Routes))
}
if cfg.Listeners[0].Routes[0].Hostname != "example.com" {
t.Fatalf("got hostname %q, want %q", cfg.Listeners[0].Routes[0].Hostname, "example.com")
}
}
func TestLoadNoDatabasePath(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.toml")
data := `
[[listeners]]
addr = ":443"
[[listeners.routes]]
hostname = "example.com"
backend = "127.0.0.1:8443"
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err)
}
_, err := Load(path)
if err == nil {
t.Fatal("expected error for missing database path")
}
}
func TestLoadNoListenersValid(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.toml")
// No listeners is valid — DB may already have them.
data := `
[database]
path = "/tmp/test.db"
[log]
level = "info"
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err)
}
_, err := Load(path)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadDuplicateHostnames(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.toml")
data := `
[database]
path = "/tmp/test.db"
[[listeners]]
addr = ":443"
[[listeners.routes]]
hostname = "example.com"
backend = "127.0.0.1:8443"
[[listeners.routes]]
hostname = "example.com"
backend = "127.0.0.1:9443"
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err)
}
_, err := Load(path)
if err == nil {
t.Fatal("expected error for duplicate hostnames")
}
}
func TestLoadGeoIPRequiredWithCountries(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.toml")
data := `
[database]
path = "/tmp/test.db"
[[listeners]]
addr = ":443"
[[listeners.routes]]
hostname = "example.com"
backend = "127.0.0.1:8443"
[firewall]
blocked_countries = ["CN"]
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err)
}
_, err := Load(path)
if err == nil {
t.Fatal("expected error for blocked_countries without geoip_db")
}
}
func TestLoadMultipleListeners(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.toml")
data := `
[database]
path = "/tmp/test.db"
[[listeners]]
addr = ":443"
[[listeners.routes]]
hostname = "public.example.com"
backend = "127.0.0.1:8443"
[[listeners]]
addr = ":8443"
[[listeners.routes]]
hostname = "internal.example.com"
backend = "127.0.0.1:9443"
`
if err := os.WriteFile(path, []byte(data), 0600); err != nil {
t.Fatalf("write config: %v", err)
}
cfg, err := Load(path)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(cfg.Listeners) != 2 {
t.Fatalf("got %d listeners, want 2", len(cfg.Listeners))
}
if cfg.Listeners[0].Routes[0].Hostname != "public.example.com" {
t.Fatalf("listener 0 hostname = %q, want %q", cfg.Listeners[0].Routes[0].Hostname, "public.example.com")
}
if cfg.Listeners[1].Routes[0].Hostname != "internal.example.com" {
t.Fatalf("listener 1 hostname = %q, want %q", cfg.Listeners[1].Routes[0].Hostname, "internal.example.com")
}
}
func TestDuration(t *testing.T) {
var d Duration
if err := d.UnmarshalText([]byte("5s")); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if d.Duration.Seconds() != 5 {
t.Fatalf("got %v, want 5s", d.Duration)
}
}