package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "debtik/internal/auth" "debtik/internal/reverseproxy" ) func setupTestProxyServer(t *testing.T) (*Server, *reverseproxy.Manager) { tmpDir := t.TempDir() cfgPath := filepath.Join(tmpDir, "reverse_proxy.json") proxyMgr := reverseproxy.NewManager(cfgPath, nil) authPath := filepath.Join(tmpDir, "users.json") authMgr := auth.NewManager(authPath) srv := &Server{ proxyMgr: proxyMgr, authMgr: authMgr, mux: http.NewServeMux(), fsHandler: http.NotFoundHandler(), } srv.registerRoutes() return srv, proxyMgr } func TestProxyAPIEndpoints(t *testing.T) { srv, _ := setupTestProxyServer(t) // 1. GET /api/proxy/config req := httptest.NewRequest(http.MethodGet, "/api/proxy/config", nil) rec := httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/proxy/config status = %d; want 200", rec.Code) } var cfg reverseproxy.Config if err := json.NewDecoder(rec.Body).Decode(&cfg); err != nil { t.Fatalf("failed to decode config: %v", err) } if !cfg.Enabled || cfg.HTTPPort != 80 { t.Errorf("unexpected config defaults: %+v", cfg) } // 2. POST /api/proxy/rules (Create Rule) newRule := reverseproxy.Rule{ Name: "Web App", Enabled: true, Domains: []string{"app.example.com"}, Path: "/app1", TargetURL: "http://10.0.0.10:8080/app1", Protocol: "both", } payload, _ := json.Marshal(newRule) req = httptest.NewRequest(http.MethodPost, "/api/proxy/rules", bytes.NewReader(payload)) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/proxy/rules status = %d; want 200, body: %s", rec.Code, rec.Body.String()) } var created reverseproxy.Rule if err := json.NewDecoder(rec.Body).Decode(&created); err != nil { t.Fatalf("failed to decode created rule: %v", err) } if created.ID == "" || created.Name != "Web App" { t.Errorf("unexpected created rule: %+v", created) } // 3. GET /api/proxy/rules req = httptest.NewRequest(http.MethodGet, "/api/proxy/rules", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/proxy/rules status = %d", rec.Code) } var rules []*reverseproxy.Rule _ = json.NewDecoder(rec.Body).Decode(&rules) if len(rules) != 1 { t.Errorf("expected 1 rule, got %d", len(rules)) } // 4. POST /api/proxy/rules/{id}/state (Toggle state) statePayload := bytes.NewReader([]byte(`{"enabled":false}`)) req = httptest.NewRequest(http.MethodPost, "/api/proxy/rules/"+created.ID+"/state", statePayload) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("POST /api/proxy/rules/{id}/state status = %d", rec.Code) } // 5. GET /api/proxy/status req = httptest.NewRequest(http.MethodGet, "/api/proxy/status", nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /api/proxy/status status = %d", rec.Code) } var status map[string]any _ = json.NewDecoder(rec.Body).Decode(&status) if status["rules_count"].(float64) != 1 { t.Errorf("expected rules_count 1, got %v", status["rules_count"]) } // 6. DELETE /api/proxy/rules/{id} req = httptest.NewRequest(http.MethodDelete, "/api/proxy/rules/"+created.ID, nil) rec = httptest.NewRecorder() srv.mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("DELETE /api/proxy/rules/{id} status = %d", rec.Code) } }