package handler import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) // An unknown project status must fail fast with a 400 and the valid list, not // surface the DB CHECK violation as a 500 (#3925: `--status active`). func TestCreateProjectInvalidStatusReturns400(t *testing.T) { w := httptest.NewRecorder() req := newRequest("POST", "/api/projects?workspace_id="+testWorkspaceID, map[string]any{ "title": "invalid status project", "status": "active", }) testHandler.CreateProject(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400 for invalid status, got %d: %s", w.Code, w.Body.String()) } if body := w.Body.String(); !strings.Contains(body, "planned") { t.Errorf("expected error to list valid statuses, got: %s", body) } } func TestCreateProjectInvalidPriorityReturns400(t *testing.T) { w := httptest.NewRecorder() req := newRequest("POST", "/api/projects?workspace_id="+testWorkspaceID, map[string]any{ "title": "invalid priority project", "priority": "critical", }) testHandler.CreateProject(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400 for invalid priority, got %d: %s", w.Code, w.Body.String()) } } // A valid status still creates the project (the validation does not over-reject). func TestCreateProjectValidStatusReturns201(t *testing.T) { w := httptest.NewRecorder() req := newRequest("POST", "/api/projects?workspace_id="+testWorkspaceID, map[string]any{ "title": "valid status project", "status": "in_progress", }) testHandler.CreateProject(w, req) if w.Code != http.StatusCreated { t.Fatalf("expected 201 for valid status, got %d: %s", w.Code, w.Body.String()) } var project ProjectResponse if err := json.NewDecoder(w.Body).Decode(&project); err != nil { t.Fatalf("decode CreateProject: %v", err) } t.Cleanup(func() { req := newRequest("DELETE", "/api/projects/"+project.ID, nil) req = withURLParam(req, "id", project.ID) testHandler.DeleteProject(httptest.NewRecorder(), req) }) if project.Status != "in_progress" { t.Errorf("expected status in_progress, got %q", project.Status) } } // Updating to an unknown status is a 400, not a 500. func TestUpdateProjectInvalidStatusReturns400(t *testing.T) { // Seed a project to update. w := httptest.NewRecorder() req := newRequest("POST", "/api/projects?workspace_id="+testWorkspaceID, map[string]any{ "title": "update validation project", }) testHandler.CreateProject(w, req) if w.Code != http.StatusCreated { t.Fatalf("seed CreateProject: %d %s", w.Code, w.Body.String()) } var project ProjectResponse if err := json.NewDecoder(w.Body).Decode(&project); err != nil { t.Fatalf("decode CreateProject: %v", err) } t.Cleanup(func() { req := newRequest("DELETE", "/api/projects/"+project.ID, nil) req = withURLParam(req, "id", project.ID) testHandler.DeleteProject(httptest.NewRecorder(), req) }) w = httptest.NewRecorder() req = newRequest("PUT", "/api/projects/"+project.ID, map[string]any{"status": "active"}) req = withURLParam(req, "id", project.ID) testHandler.UpdateProject(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400 for invalid update status, got %d: %s", w.Code, w.Body.String()) } }