package render import ( "encoding/json" "testing" "webui4frpc/internal/store" ) func TestRenderTCPProxy(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret"} data, err := Render(remote, []Proxy{ {Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080}, }) if err != nil { t.Fatal(err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } if cfg.ServerAddr != "1.2.3.4" || cfg.Auth.Token != "secret" { t.Fatalf("cfg = %+v", cfg) } if len(cfg.Proxies) != 1 || cfg.Proxies[0].RemotePort != 8080 { t.Fatalf("proxies = %+v", cfg.Proxies) } } func TestRenderDuplicateServiceGetsPortSuffix(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000} proxy := []Proxy{ {Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080}, {Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8081}, } data, err := Render(remote, proxy) if err != nil { t.Fatal(err) } var cfg Config _ = json.Unmarshal(data, &cfg) if len(cfg.Proxies) != 2 { t.Fatalf("proxies = %+v", cfg.Proxies) } if cfg.Proxies[1].Name != "web-8081" { t.Fatalf("second proxy name = %q", cfg.Proxies[1].Name) } } func TestRenderTransportAdvanced(t *testing.T) { remote := store.Remote{ Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret", TransportProtocol: "kcp", TransportTLS: true, TransportPool: 3, TransportTLSServerName: "frps.local", } data, err := Render(remote, []Proxy{ {Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080, UseEncryption: true, UseCompression: true, BandwidthLimit: "1MB", PoolCount: 2, Metadatas: map[string]string{"env": "prod"}, Annotations: map[string]string{"owner": "ops"}}, }) if err != nil { t.Fatal(err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } if cfg.Transport.Protocol != "kcp" { t.Fatalf("transport.protocol = %q, want kcp", cfg.Transport.Protocol) } if cfg.Transport.TLS == nil || !cfg.Transport.TLS.Enable || cfg.Transport.TLS.ServerName != "frps.local" { t.Fatalf("transport.tls = %+v", cfg.Transport.TLS) } if cfg.Transport.PoolCount != 3 { t.Fatalf("transport.poolCount = %d, want 3", cfg.Transport.PoolCount) } p := cfg.Proxies[0] if !p.UseEncryption || !p.UseCompression || p.PoolCount != 2 { t.Fatalf("proxy advanced fields = %+v", p) } if p.Metadatas["env"] != "prod" || p.Annotations["owner"] != "ops" { t.Fatalf("proxy metadatas/annotations = %+v", p) } if p.Transport == nil || p.Transport.BandwidthLimit != "1MB" { t.Fatalf("proxy transport.bandwidthLimit = %+v", p.Transport) } } func TestRenderTransportOmittedWhenEmpty(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000} data, err := Render(remote, []Proxy{ {Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080}, }) if err != nil { t.Fatal(err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } if cfg.Transport.Protocol != "" || cfg.Transport.TLS != nil || cfg.Transport.PoolCount != 0 { t.Fatalf("transport should be empty, got %+v", cfg.Transport) } if cfg.Proxies[0].UseEncryption || cfg.Proxies[0].Transport != nil { t.Fatalf("proxy advanced should be empty, got %+v", cfg.Proxies[0]) } } func TestRenderHTTPAdvanced(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret"} data, err := Render(remote, []Proxy{ {Name: "web", Type: "http", LocalIP: "127.0.0.1", LocalPort: 8080, CustomDomains: []string{"app.example.com"}, Locations: []string{"/api", "/admin"}, HostHeaderRewrite: "backend.internal", HTTPHeaders: map[string]string{"X-Custom": "val"}, BasicAuthUser: "user", BasicAuthPassword: "pass"}, }) if err != nil { t.Fatal(err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } p := cfg.Proxies[0] if p.CustomDomains[0] != "app.example.com" { t.Fatalf("customDomains = %+v", p.CustomDomains) } if len(p.Locations) != 2 || p.Locations[0] != "/api" { t.Fatalf("locations = %+v", p.Locations) } if p.HostHeaderRewrite != "backend.internal" { t.Fatalf("hostHeaderRewrite = %q", p.HostHeaderRewrite) } if p.HTTPHeaders["X-Custom"] != "val" { t.Fatalf("httpHeaders = %+v", p.HTTPHeaders) } if p.HTTPBasicAuth == nil || p.HTTPBasicAuth.User != "user" || p.HTTPBasicAuth.Password != "pass" { t.Fatalf("basicAuth = %+v", p.HTTPBasicAuth) } } func TestRenderHTTPSUsesCustomDomains(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000} data, err := Render(remote, []Proxy{ {Name: "secure", Type: "https", LocalIP: "127.0.0.1", LocalPort: 8443, CustomDomains: []string{"secure.example.com"}}, }) if err != nil { t.Fatal(err) } var cfg Config _ = json.Unmarshal(data, &cfg) p := cfg.Proxies[0] if p.RemotePort != 0 { t.Fatalf("https proxy should not set remotePort, got %d", p.RemotePort) } if len(p.CustomDomains) != 1 || p.CustomDomains[0] != "secure.example.com" { t.Fatalf("customDomains = %+v", p.CustomDomains) } } func TestRenderLoadBalancerAndHealthCheck(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret"} data, err := Render(remote, []Proxy{ {Name: "web1", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080, LBGroup: "web", LBGroupKey: "k", HealthCheckType: "tcp", HealthCheckTimeout: 3, HealthCheckMaxFailed: 2, HealthCheckInterval: 5}, }) if err != nil { t.Fatal(err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } p := cfg.Proxies[0] if p.LoadBalancer == nil || p.LoadBalancer.Group != "web" || p.LoadBalancer.GroupKey != "k" { t.Fatalf("loadBalancer = %+v", p.LoadBalancer) } if p.HealthCheck == nil || p.HealthCheck.Type != "tcp" || p.HealthCheck.IntervalSeconds != 5 || p.HealthCheck.MaxFailed != 2 || p.HealthCheck.TimeoutSeconds != 3 { t.Fatalf("healthCheck = %+v", p.HealthCheck) } } func TestRenderHealthCheckHTTPWithHeaders(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000} data, err := Render(remote, []Proxy{ {Name: "web", Type: "http", LocalIP: "127.0.0.1", LocalPort: 8080, CustomDomains: []string{"app.example.com"}, HealthCheckType: "http", HealthCheckPath: "/healthz", HealthCheckTimeout: 2, HealthCheckMaxFailed: 3, HealthCheckInterval: 7, HTTPHeaders: map[string]string{"X-Probe": "1"}}, }) if err != nil { t.Fatal(err) } var cfg Config _ = json.Unmarshal(data, &cfg) p := cfg.Proxies[0] if p.HealthCheck == nil || p.HealthCheck.Type != "http" || p.HealthCheck.Path != "/healthz" || p.HealthCheck.IntervalSeconds != 7 { t.Fatalf("healthCheck = %+v", p.HealthCheck) } if p.HealthCheck.HTTPHeaders == nil || len(p.HealthCheck.HTTPHeaders) == 0 || p.HealthCheck.HTTPHeaders[0].Name != "X-Probe" || p.HealthCheck.HTTPHeaders[0].Value != "1" { t.Fatalf("healthCheck.httpHeaders = %+v", p.HealthCheck.HTTPHeaders) } // http proxy must still use customDomains and no remotePort if len(p.CustomDomains) != 1 || p.CustomDomains[0] != "app.example.com" || p.RemotePort != 0 { t.Fatalf("http proxy routing = customDomains=%v remotePort=%d", p.CustomDomains, p.RemotePort) } } func TestRenderHealthCheckOmittedWhenOff(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000} data, err := Render(remote, []Proxy{ {Name: "web", Type: "tcp", LocalIP: "127.0.0.1", LocalPort: 8080, RemotePort: 8080}, }) if err != nil { t.Fatal(err) } var cfg Config _ = json.Unmarshal(data, &cfg) p := cfg.Proxies[0] if p.LoadBalancer != nil || p.HealthCheck != nil { t.Fatalf("lb/health should be omitted, got lb=%+v hc=%+v", p.LoadBalancer, p.HealthCheck) } } func TestRenderAdminWebServer(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret", AdminAddr: "127.0.0.1", AdminPort: 7400, AdminUser: "admin", AdminPassword: "pw"} data, err := Render(remote, nil) if err != nil { t.Fatal(err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } if cfg.WebServer == nil { t.Fatal("webServer missing when AdminPort set") } if cfg.WebServer.Port != 7400 || cfg.WebServer.Addr != "127.0.0.1" || cfg.WebServer.User != "admin" || cfg.WebServer.Password != "pw" { t.Fatalf("webServer = %+v", cfg.WebServer) } } func TestRenderAdminWebServerOmitted(t *testing.T) { remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000} data, err := Render(remote, nil) if err != nil { t.Fatal(err) } var cfg Config _ = json.Unmarshal(data, &cfg) if cfg.WebServer != nil { t.Fatalf("webServer should be omitted, got %+v", cfg.WebServer) } }