package tunnel import ( "os" "path/filepath" "strings" "testing" ) func testConfig() Config { return Config{ Enabled: true, TunnelID: "abc-123", PublicDomain: "pub.payne.io", GatewayDomain: "payne.io", CredentialsDir: "/tmp/creds", } } func TestGenerateConfig_Basic(t *testing.T) { m := NewManager(t.TempDir()) services := []PublicService{ {Name: "my-api"}, {Name: "dashboard"}, } out := m.GenerateConfig(testConfig(), 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, "credentials-file: /tmp/creds/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 := []PublicService{ {Name: "internal-name", Subdomain: "public-name"}, } out := m.GenerateConfig(testConfig(), 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() cfg.Enabled = false out := m.GenerateConfig(cfg, []PublicService{{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(), 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() cfg.TunnelID = "" out := m.GenerateConfig(cfg, []PublicService{{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 := []PublicService{{Name: "my-api"}} if err := m.WriteConfig(testConfig(), 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(), []PublicService{{Name: "my-api"}}); err != nil { t.Fatalf("WriteConfig failed: %v", err) } // Now disable and write again — should remove the file cfg := testConfig() cfg.Enabled = false if err := m.WriteConfig(cfg, []PublicService{{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") } }