package haproxy import ( "bytes" "strings" "testing" "git.netcell-it.de/projekte/edgeguard-native/internal/models" ) func renderView(t *testing.T, v View) string { t.Helper() var buf bytes.Buffer if err := tpl.Execute(&buf, v); err != nil { t.Fatalf("template execute: %v", err) } return buf.String() } func mkBackend(id int64, name string, hcp *string) models.Backend { return models.Backend{ ID: id, Name: name, Scheme: "http", LBAlgorithm: "roundrobin", Active: true, HealthCheckPath: hcp, } } func mkServer(backendID int64, name, addr string, port int) models.BackendServer { return models.BackendServer{ BackendID: backendID, Name: name, Address: addr, Port: port, Weight: 100, Active: true, } } func TestRender_BaselineHasFrontendsAndApiBackend(t *testing.T) { out := renderView(t, View{}) for _, w := range []string{ "frontend public_http", "frontend public_https", "frontend internal_stats", "backend api_backend", "server api1 127.0.0.1:9443 check", "bind :443 ssl crt /etc/edgeguard/tls/", "path_beg /.well-known/acme-challenge/", "http-request redirect scheme https", } { if !strings.Contains(out, w) { t.Errorf("missing %q in baseline output:\n%s", w, out) } } } func TestRender_DomainRoutesEmitUseBackend(t *testing.T) { v := View{ Backends: []BackendView{ {Backend: mkBackend(1, "app", nil), Servers: []models.BackendServer{ mkServer(1, "app", "10.0.0.10", 8080), }}, {Backend: mkBackend(2, "api", nil), Servers: []models.BackendServer{ mkServer(2, "api", "10.0.0.20", 9000), }}, }, Domains: []DomainView{{ Domain: models.Domain{ID: 1, Name: "example.com", Active: true}, Routes: []RouteView{ {PathPrefix: "/", BackendID: 1}, {PathPrefix: "/api", BackendID: 2}, }, }}, } out := renderView(t, v) for _, w := range []string{ "backend eg_backend_1", "server app 10.0.0.10:8080", "backend eg_backend_2", "server api 10.0.0.20:9000", "balance roundrobin", "use_backend eg_backend_1 if { hdr(host) -i example.com } { path_beg / }", "use_backend eg_backend_2 if { hdr(host) -i example.com } { path_beg /api }", } { if !strings.Contains(out, w) { t.Errorf("missing %q in output:\n%s", w, out) } } } func TestRender_HealthCheckPathAddsCheckInter(t *testing.T) { hcp := "/health" v := View{ Backends: []BackendView{ {Backend: mkBackend(1, "app", &hcp), Servers: []models.BackendServer{ mkServer(1, "app", "10.0.0.10", 8080), }}, }, } out := renderView(t, v) if !strings.Contains(out, "server app 10.0.0.10:8080 check inter 5s") { t.Errorf("expected `check inter 5s` for backend with health_check_path:\n%s", out) } if !strings.Contains(out, "option httpchk") { t.Errorf("expected `option httpchk` when health_check_path set:\n%s", out) } } func TestRender_HTTPSHealthcheckPinsAlpnHTTP1(t *testing.T) { // L7TOUT-Bug: ohne `check-alpn http/1.1` handelt der Check h2 // aus (vom server-Stmt geerbt) und hängt, weil option httpchk // HTTP/1.x sendet. Test stellt sicher dass HTTPS+Healthcheck // das ALPN für den Check pinnt. hcp := "/" v := View{ Backends: []BackendView{ { Backend: models.Backend{ID: 9, Name: "tls-app", Scheme: "https", LBAlgorithm: "roundrobin", HealthCheckPath: &hcp, Active: true}, Servers: []models.BackendServer{ {BackendID: 9, Name: "tls-1", Address: "10.0.0.30", Port: 8443, Weight: 100, Active: true}, }, }, { // Gegenprobe: HTTP-Backend mit Healthcheck darf KEIN // check-alpn bekommen (ALPN gibt's nur bei SSL). Backend: models.Backend{ID: 10, Name: "plain-app", Scheme: "http", LBAlgorithm: "roundrobin", HealthCheckPath: &hcp, Active: true}, Servers: []models.BackendServer{ {BackendID: 10, Name: "plain-1", Address: "10.0.0.31", Port: 80, Weight: 100, Active: true}, }, }, }, } out := renderView(t, v) idxTLS := strings.Index(out, "backend eg_backend_9") idxPlain := strings.Index(out, "backend eg_backend_10") if idxTLS < 0 || idxPlain < 0 { t.Fatalf("backend sections missing:\n%s", out) } tlsBlock := out[idxTLS:idxPlain] plainBlock := out[idxPlain:] if !strings.Contains(tlsBlock, "check-alpn http/1.1") { t.Errorf("HTTPS+healthcheck soll check-alpn http/1.1 pinnen:\n%s", tlsBlock) } if strings.Contains(plainBlock, "check-alpn") { t.Errorf("HTTP-Backend darf KEIN check-alpn bekommen:\n%s", plainBlock) } } func TestRender_WebSocketEmitsTunnelTimeout(t *testing.T) { v := View{ Backends: []BackendView{ { Backend: models.Backend{ID: 7, Name: "vmm", Scheme: "https", LBAlgorithm: "source", WebSocket: true, Active: true}, Servers: []models.BackendServer{ {BackendID: 7, Name: "vmm-1", Address: "10.0.5.14", Port: 8006, Weight: 100, Active: true}, }, }, { Backend: models.Backend{ID: 8, Name: "api", Scheme: "http", LBAlgorithm: "roundrobin", WebSocket: false, Active: true}, Servers: []models.BackendServer{ {BackendID: 8, Name: "api-1", Address: "10.0.5.20", Port: 8080, Weight: 100, Active: true}, }, }, }, } out := renderView(t, v) // vmm soll tunnel-Timeout haben, api nicht. idxVmm := strings.Index(out, "backend eg_backend_7") idxApi := strings.Index(out, "backend eg_backend_8") if idxVmm < 0 || idxApi < 0 { t.Fatalf("backend sections missing in output:\n%s", out) } vmmBlock := out[idxVmm:idxApi] apiBlock := out[idxApi:] if !strings.Contains(vmmBlock, "timeout tunnel 1h") { t.Errorf("vmm-Block sollte `timeout tunnel 1h` enthalten:\n%s", vmmBlock) } if strings.Contains(apiBlock, "timeout tunnel") { t.Errorf("api-Block soll KEIN `timeout tunnel` enthalten:\n%s", apiBlock) } } func TestRender_MultiServerPool(t *testing.T) { v := View{ Backends: []BackendView{ { Backend: models.Backend{ID: 1, Name: "vmm", Scheme: "http", LBAlgorithm: "leastconn", Active: true}, Servers: []models.BackendServer{ {BackendID: 1, Name: "vmm-1", Address: "10.0.0.11", Port: 8080, Weight: 100, Active: true}, {BackendID: 1, Name: "vmm-2", Address: "10.0.0.12", Port: 8080, Weight: 100, Active: true}, {BackendID: 1, Name: "vmm-3", Address: "10.0.0.13", Port: 8080, Weight: 50, Backup: true, Active: true}, }, }, }, } out := renderView(t, v) for _, w := range []string{ "backend eg_backend_1", "balance leastconn", "server vmm-1 10.0.0.11:8080", "server vmm-2 10.0.0.12:8080", "server vmm-3 10.0.0.13:8080", "weight 50", " backup", } { if !strings.Contains(out, w) { t.Errorf("missing %q in multi-server output:\n%s", w, out) } } }