package main import ( "context" "errors" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" "github.com/oracle/oci-go-sdk/v65/core" ) func TestWireGuardIgnoresWebHealth(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/metrics" { t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path) } _, _ = w.Write([]byte("oci_l4_relay_component_healthy{component=\"web_allow\"} 0\n")) })) defer server.Close() if err := waitForWireGuard(context.Background(), server.URL+"/metrics", time.Second); err != nil { t.Fatal(err) } } func TestWireGuardRejectsUnavailableTunnel(t *testing.T) { for _, status := range []int{http.StatusBadGateway, http.StatusServiceUnavailable} { t.Run(http.StatusText(status), func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(status) })) defer server.Close() err := waitForWireGuard(context.Background(), server.URL+"/metrics", 20*time.Millisecond) if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected recovery deadline, got %v", err) } }) } } func TestWireGuardDoesNotFollowRedirects(t *testing.T) { var calls atomic.Int32 destination := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { calls.Add(1) w.WriteHeader(http.StatusOK) })) defer destination.Close() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, destination.URL, http.StatusFound) })) defer server.Close() err := waitForWireGuard(context.Background(), server.URL+"/metrics", 20*time.Millisecond) if !errors.Is(err, context.DeadlineExceeded) || calls.Load() != 0 { t.Fatalf("error = %v, redirected calls = %d", err, calls.Load()) } } func TestWireGuardCancelsBlockedRequest(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { <-r.Context().Done() })) defer server.Close() ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() if err := waitForWireGuard(ctx, server.URL+"/metrics", time.Minute); !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected cancellation, got %v", err) } } func TestPollStopsUpdatingAfterWireGuardRecovery(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() observations := 0 opnsense := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { observations++ if observations == 5 { cancel() } _, _ = w.Write([]byte(`{"rows":[{"identifier":"wan","description":"WAN","ipv4":[{"ipaddr":"203.0.113.7"}]}]}`)) })) defer opnsense.Close() var probes atomic.Int32 tunnel := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { probes.Add(1) _, _ = w.Write([]byte("oci_l4_relay_component_healthy{component=\"web_allow\"} 0\n")) })) defer tunnel.Close() nsg := &recordingNSGClient{rules: []core.SecurityRule{returnedRule("rule", "203.0.113.7/32", 51820)}} reconciler := &ociReconciler{ client: nsg, networkSecurityGroup: "nsg", securityRule: "rule", wireguardDestinationPort: 51820, wireguardURL: tunnel.URL + "/metrics", recoveryTimeout: time.Second, probeWireGuard: waitForWireGuard, } client := &opnsenseClient{baseURL: opnsense.URL, http: opnsense.Client()} _ = poll(ctx, client, "WAN", time.Millisecond, 2, reconciler) if observations != 5 || nsg.calls != 1 || probes.Load() != 1 { t.Fatalf("observations = %d, NSG updates = %d, tunnel probes = %d", observations, nsg.calls, probes.Load()) } } func TestMetricsURL(t *testing.T) { for _, value := range []string{"", "https://relay.test/metrics", "http://relay.test/", "http://relay.test/metrics?query=1", "http://key:secret@relay.test/metrics"} { t.Run(value, func(t *testing.T) { t.Setenv("WG_STATUS_URL", value) if _, err := requiredMetricsURL("WG_STATUS_URL"); err == nil { t.Fatal("expected invalid endpoint rejection") } }) } t.Setenv("WG_STATUS_URL", "http://relay.test:19100/metrics") if _, err := requiredMetricsURL("WG_STATUS_URL"); err != nil { t.Fatal(err) } }