Files
wild-central/internal/tunnel/manager_test.go
Paul Payne 2bb162a794 Add per-subsystem config validation for wireguard, authelia, nftables, tunnel
- wireguard: ValidateConfig checks ListenPort range, Address/LanCIDR CIDR
  format. SaveConfig now validates before writing.
- authelia: ValidateConfigOpts checks required fields (Domain, JWTSecret,
  SessionSecret) and StorageEncKey minimum length (20 chars). GenerateConfig
  now validates before generating.
- nftables: ValidateWANInterface checks interface exists via net.InterfaceByName
  before generating rules that reference it.
- tunnel: ValidateConfig checks TunnelID, PublicDomain, GatewayDomain are set
  and credentials file exists. WriteConfig now validates before generating.
2026-07-14 11:54:58 +00:00

163 lines
4.2 KiB
Go

package tunnel
import (
"os"
"path/filepath"
"strings"
"testing"
)
func testConfig(t *testing.T) Config {
t.Helper()
credsDir := filepath.Join(t.TempDir(), "creds")
os.MkdirAll(credsDir, 0755)
os.WriteFile(filepath.Join(credsDir, "abc-123.json"), []byte(`{"AccountTag":"test"}`), 0600)
return Config{
Enabled: true,
TunnelID: "abc-123",
PublicDomain: "pub.payne.io",
GatewayDomain: "payne.io",
CredentialsDir: credsDir,
}
}
func TestGenerateConfig_Basic(t *testing.T) {
m := NewManager(t.TempDir())
services := []PublicDomain{
{Name: "my-api"},
{Name: "dashboard"},
}
cfg := testConfig(t)
out := m.GenerateConfig(cfg, services)
if out == "" {
t.Fatal("expected non-empty config")
}
// Check tunnel ID
if !strings.Contains(out, "tunnel: abc-123") {
t.Errorf("expected tunnel ID, got:\n%s", out)
}
// Check credentials file
if !strings.Contains(out, "abc-123.json") {
t.Errorf("expected credentials file, got:\n%s", out)
}
// Check public hostnames
if !strings.Contains(out, "hostname: my-api.pub.payne.io") {
t.Errorf("expected my-api public hostname, got:\n%s", out)
}
if !strings.Contains(out, "hostname: dashboard.pub.payne.io") {
t.Errorf("expected dashboard public hostname, got:\n%s", out)
}
// Check Host/SNI rewriting to internal domain
if !strings.Contains(out, "originServerName: my-api.payne.io") {
t.Errorf("expected internal SNI for my-api, got:\n%s", out)
}
if !strings.Contains(out, "httpHostHeader: dashboard.payne.io") {
t.Errorf("expected internal Host header for dashboard, got:\n%s", out)
}
// Check catch-all
if !strings.Contains(out, "service: http_status:404") {
t.Errorf("expected catch-all 404, got:\n%s", out)
}
// Check gateway origin
if !strings.Contains(out, "service: https://localhost:443") {
t.Errorf("expected gateway origin, got:\n%s", out)
}
}
func TestGenerateConfig_SubdomainOverride(t *testing.T) {
m := NewManager(t.TempDir())
services := []PublicDomain{
{Name: "internal-name", Subdomain: "public-name"},
}
out := m.GenerateConfig(testConfig(t), services)
if !strings.Contains(out, "hostname: public-name.pub.payne.io") {
t.Errorf("expected subdomain override, got:\n%s", out)
}
}
func TestGenerateConfig_Disabled(t *testing.T) {
m := NewManager(t.TempDir())
cfg := testConfig(t)
cfg.Enabled = false
out := m.GenerateConfig(cfg, []PublicDomain{{Name: "my-api"}})
if out != "" {
t.Errorf("expected empty config when disabled, got:\n%s", out)
}
}
func TestGenerateConfig_NoServices(t *testing.T) {
m := NewManager(t.TempDir())
out := m.GenerateConfig(testConfig(t), nil)
if out != "" {
t.Errorf("expected empty config with no services, got:\n%s", out)
}
}
func TestGenerateConfig_MissingTunnelID(t *testing.T) {
m := NewManager(t.TempDir())
cfg := testConfig(t)
cfg.TunnelID = ""
out := m.GenerateConfig(cfg, []PublicDomain{{Name: "my-api"}})
if out != "" {
t.Errorf("expected empty config with missing tunnel ID, got:\n%s", out)
}
}
func TestWriteConfig(t *testing.T) {
tmpDir := t.TempDir()
m := NewManager(tmpDir)
services := []PublicDomain{{Name: "my-api"}}
if err := m.WriteConfig(testConfig(t), services); err != nil {
t.Fatalf("WriteConfig failed: %v", err)
}
// Verify file was written
configPath := filepath.Join(tmpDir, "tunnel", "config.yml")
data, err := os.ReadFile(configPath)
if err != nil {
t.Fatalf("Failed to read config: %v", err)
}
if !strings.Contains(string(data), "my-api.pub.payne.io") {
t.Errorf("expected my-api hostname in written config, got:\n%s", string(data))
}
}
func TestWriteConfig_RemovesWhenDisabled(t *testing.T) {
tmpDir := t.TempDir()
m := NewManager(tmpDir)
// Write config first
if err := m.WriteConfig(testConfig(t), []PublicDomain{{Name: "my-api"}}); err != nil {
t.Fatalf("WriteConfig failed: %v", err)
}
// Now disable and write again — should remove the file
cfg := testConfig(t)
cfg.Enabled = false
if err := m.WriteConfig(cfg, []PublicDomain{{Name: "my-api"}}); err != nil {
t.Fatalf("WriteConfig (disable) failed: %v", err)
}
configPath := filepath.Join(tmpDir, "tunnel", "config.yml")
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
t.Error("expected config file to be removed when disabled")
}
}