package v1 import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) func setupTestHaproxy(t *testing.T) (*API, string) { tmpDir := t.TempDir() haproxyPath := filepath.Join(tmpDir, "haproxy.cfg") t.Setenv("WILD_CENTRAL_HAPROXY_CONFIG_PATH", haproxyPath) t.Setenv("WILD_CENTRAL_NFTABLES_RULES_PATH", filepath.Join(tmpDir, "wild-central.nft")) api, err := NewAPI(tmpDir, "", nil) if err != nil { t.Fatalf("failed to create test API: %v", err) } api.haproxy.SetSocketPath(filepath.Join(tmpDir, "haproxy.sock")) return api, tmpDir } func TestHaproxyStatus_ReturnsStatus(t *testing.T) { api, _ := setupTestHaproxy(t) req := httptest.NewRequest("GET", "/api/v1/haproxy/status", nil) w := httptest.NewRecorder() api.HaproxyStatus(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]any if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("invalid JSON: %v", err) } if _, ok := resp["status"]; !ok { t.Error("expected 'status' field in response") } } func TestHaproxyGetConfig_WhenFileExists(t *testing.T) { api, tmpDir := setupTestHaproxy(t) configPath := filepath.Join(tmpDir, "haproxy.cfg") content := "# test haproxy config\nglobal\n daemon\n" if err := os.WriteFile(configPath, []byte(content), 0644); err != nil { t.Fatalf("failed to write config: %v", err) } req := httptest.NewRequest("GET", "/api/v1/haproxy/config", nil) w := httptest.NewRecorder() api.HaproxyGetConfig(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]any if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("invalid JSON: %v", err) } if got, _ := resp["content"].(string); got != content { t.Errorf("content mismatch: got %q, want %q", got, content) } } func TestHaproxyGetConfig_WhenFileMissing(t *testing.T) { api, _ := setupTestHaproxy(t) req := httptest.NewRequest("GET", "/api/v1/haproxy/config", nil) w := httptest.NewRecorder() api.HaproxyGetConfig(w, req) if w.Code != http.StatusInternalServerError { t.Fatalf("expected 500, got %d: %s", w.Code, w.Body.String()) } } func TestHaproxyStats_WhenSocketMissing(t *testing.T) { api, _ := setupTestHaproxy(t) req := httptest.NewRequest("GET", "/api/v1/haproxy/stats", nil) w := httptest.NewRecorder() api.HaproxyStats(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]any if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("invalid JSON: %v", err) } backends, ok := resp["backends"].([]any) if !ok { t.Fatalf("expected 'backends' array, got %T: %v", resp["backends"], resp["backends"]) } if len(backends) != 0 { t.Errorf("expected empty backends, got %d", len(backends)) } } func TestHaproxyGenerate_MissingState(t *testing.T) { api, tmpDir := setupTestHaproxy(t) os.Remove(filepath.Join(tmpDir, "state.yaml")) req := httptest.NewRequest("POST", "/api/v1/haproxy/generate", nil) w := httptest.NewRecorder() api.HaproxyGenerate(w, req) // Missing state.yaml is handled gracefully — reconciliation proceeds // with empty config, generating a minimal HAProxy config. if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } } func TestHaproxyGenerate_NoServices(t *testing.T) { api, _ := setupTestHaproxy(t) req := httptest.NewRequest("POST", "/api/v1/haproxy/generate", nil) w := httptest.NewRecorder() api.HaproxyGenerate(w, req) // 200 on device, 500 in CI (no systemctl) if w.Code != http.StatusOK && w.Code != http.StatusInternalServerError { t.Errorf("unexpected status %d: %s", w.Code, w.Body.String()) } }