diff --git a/backend/controllers/healthcheck.go b/backend/controllers/healthcheck.go new file mode 100644 index 00000000..5d555496 --- /dev/null +++ b/backend/controllers/healthcheck.go @@ -0,0 +1,20 @@ +package controllers + +import ( + "encoding/json" + "net/http" +) + +func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{ + "status": "healthy", + "service": "ccsync-backend", + }) +} diff --git a/backend/controllers/healthcheck_test.go b/backend/controllers/healthcheck_test.go new file mode 100644 index 00000000..9a58a3b8 --- /dev/null +++ b/backend/controllers/healthcheck_test.go @@ -0,0 +1,56 @@ +package controllers + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthCheckHandler(t *testing.T) { + req, err := http.NewRequest("GET", "/health", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(HealthCheckHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + } + + expected := "application/json" + if contentType := rr.Header().Get("Content-Type"); contentType != expected { + t.Errorf("handler returned wrong content type: got %v want %v", contentType, expected) + } + + var response map[string]string + if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { + t.Errorf("could not parse response JSON: %v", err) + } + + if response["status"] != "healthy" { + t.Errorf("expected status to be 'healthy', got %v", response["status"]) + } + + if response["service"] != "ccsync-backend" { + t.Errorf("expected service to be 'ccsync-backend', got %v", response["service"]) + } +} + +func TestHealthCheckHandlerMethodNotAllowed(t *testing.T) { + req, err := http.NewRequest("POST", "/health", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(HealthCheckHandler) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusMethodNotAllowed { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusMethodNotAllowed) + } +} diff --git a/backend/main.go b/backend/main.go index 53164591..ef81990f 100644 --- a/backend/main.go +++ b/backend/main.go @@ -100,6 +100,8 @@ func main() { mux.Handle("/delete-task", rateLimitedHandler(http.HandlerFunc(controllers.DeleteTaskHandler))) mux.Handle("/sync/logs", rateLimitedHandler(http.HandlerFunc(controllers.SyncLogsHandler))) + mux.HandleFunc("/health", controllers.HealthCheckHandler) + mux.HandleFunc("/ws", controllers.WebSocketHandler) // API documentation endpoint