package reverseproxy import ( "fmt" "io" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) func TestReverseProxyManagerEndToEnd(t *testing.T) { // 1. Setup mock backend server that echoes received request path and headers backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Echo-Path", r.URL.Path) w.Header().Set("X-Echo-Query", r.URL.RawQuery) w.Header().Set("X-Echo-Host", r.Host) w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, "OK: %s", r.URL.Path) })) defer backend.Close() // 2. Setup manager with temporary config file tmpDir := t.TempDir() cfgPath := filepath.Join(tmpDir, "reverse_proxy.json") mgr := NewManager(cfgPath, nil) defer mgr.Stop() // 3. Add Rule for /app1 pointing to backend/app1 rule1, err := mgr.SaveRule(&Rule{ Name: "App1 Rule", Enabled: true, Domains: []string{"example.com"}, Path: "/app1", TargetURL: backend.URL + "/app1", StripPath: false, }) if err != nil { t.Fatalf("failed to save rule: %v", err) } // 4. Test request to /app1/module1/attribute1 req := httptest.NewRequest(http.MethodGet, "http://example.com/app1/module1/attribute1?page=2", nil) rec := httptest.NewRecorder() handled := mgr.HandleHTTP(rec, req) if !handled { t.Fatalf("expected request to be handled by reverse proxy") } res := rec.Result() if res.StatusCode != http.StatusOK { t.Fatalf("unexpected status code: %d", res.StatusCode) } if got := res.Header.Get("X-Echo-Path"); got != "/app1/module1/attribute1" { t.Errorf("X-Echo-Path = %q; want %q", got, "/app1/module1/attribute1") } if got := res.Header.Get("X-Echo-Query"); got != "page=2" { t.Errorf("X-Echo-Query = %q; want %q", got, "page=2") } body, _ := io.ReadAll(res.Body) if string(body) != "OK: /app1/module1/attribute1" { t.Errorf("body = %q; want %q", string(body), "OK: /app1/module1/attribute1") } // 5. Test Rule Stats updatedRule, err := mgr.GetRule(rule1.ID) if err != nil { t.Fatalf("failed to get rule: %v", err) } if updatedRule.Stats.Requests != 1 { t.Errorf("expected 1 request in stats, got %d", updatedRule.Stats.Requests) } // 6. Test Request to unmapped path /unmapped - should NOT be handled req2 := httptest.NewRequest(http.MethodGet, "http://example.com/unmapped", nil) rec2 := httptest.NewRecorder() if mgr.HandleHTTP(rec2, req2) { t.Errorf("expected unmapped request to NOT be handled by proxy") } // 7. Test StripPath option rule2, err := mgr.SaveRule(&Rule{ Name: "Strip Path Rule", Enabled: true, Domains: []string{"strip.example.com"}, Path: "/service", TargetURL: backend.URL, StripPath: true, }) if err != nil { t.Fatalf("failed to save rule2: %v", err) } req3 := httptest.NewRequest(http.MethodGet, "http://strip.example.com/service/sub/item", nil) rec3 := httptest.NewRecorder() if !mgr.HandleHTTP(rec3, req3) { t.Fatalf("expected strip path request to be handled") } res3 := rec3.Result() if got := res3.Header.Get("X-Echo-Path"); got != "/sub/item" { t.Errorf("X-Echo-Path with stripPath = %q; want %q", got, "/sub/item") } // 8. Test HTTP to HTTPS redirect _, err = mgr.SaveRule(&Rule{ Name: "HTTPS Only Rule", Enabled: true, Domains: []string{"secure.example.com"}, Path: "/", TargetURL: backend.URL, Protocol: ProtocolHTTPS, HTTPToHTTPS: true, }) if err != nil { t.Fatalf("failed to save https rule: %v", err) } req4 := httptest.NewRequest(http.MethodGet, "http://secure.example.com/login", nil) rec4 := httptest.NewRecorder() if !mgr.HandleHTTP(rec4, req4) { t.Fatalf("expected HTTP to HTTPS redirect to be handled") } res4 := rec4.Result() if res4.StatusCode != http.StatusMovedPermanently { t.Errorf("expected status 301, got %d", res4.StatusCode) } if loc := res4.Header.Get("Location"); loc != "https://secure.example.com/login" { t.Errorf("redirect Location = %q; want %q", loc, "https://secure.example.com/login") } // Clean up temp file _ = os.Remove(cfgPath) _ = rule2 }