package handlers import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/go-chi/chi/v5" "task-dashboard/internal/models" ) func withURLParam(req *http.Request, key, value string) *http.Request { rctx := chi.NewRouteContext() rctx.URLParams.Add(key, value) return req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) } func TestHandleWidgetChainsCreate_CreatesChain(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() h := &Handler{store: db} body := `{"name":"Ham Radio Track","tasks":[{"content":"Study Technician"},{"content":"Pass exam","description":"Pick a date","priority":3}]}` req := httptest.NewRequest("POST", "/api/widget/chains", strings.NewReader(body)) w := httptest.NewRecorder() h.HandleWidgetChainsCreate(w, req) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200, body=%s", w.Code, w.Body.String()) } var resp chainCreateResponse if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { t.Fatal(err) } if resp.ID == "" { t.Fatal("expected a generated chain id") } tasks, err := h.store.GetChainTasks(resp.ID) if err != nil { t.Fatal(err) } if len(tasks) != 2 { t.Fatalf("len(tasks) = %d, want 2", len(tasks)) } } func TestHandleWidgetChainsCreate_EmptyTasks_Returns400(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() h := &Handler{store: db} body := `{"name":"Empty","tasks":[]}` req := httptest.NewRequest("POST", "/api/widget/chains", strings.NewReader(body)) w := httptest.NewRecorder() h.HandleWidgetChainsCreate(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("status = %d, want 400", w.Code) } } func TestHandleWidgetChainGet_ReturnsChainAndTasks(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() h := &Handler{store: db} chain, err := h.store.CreateChain("Track", []models.ChainTaskInput{{Content: "Step 1"}, {Content: "Step 2"}}) if err != nil { t.Fatal(err) } req := withURLParam(httptest.NewRequest("GET", "/api/widget/chains/"+chain.ID, nil), "id", chain.ID) w := httptest.NewRecorder() h.HandleWidgetChainGet(w, req) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200, body=%s", w.Code, w.Body.String()) } var resp chainGetResponse if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { t.Fatal(err) } if len(resp.Tasks) != 2 || resp.Chain.ID != chain.ID { t.Errorf("resp = %+v", resp) } } func TestHandleWidgetChainGet_UnknownID_Returns404(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() h := &Handler{store: db} req := withURLParam(httptest.NewRequest("GET", "/api/widget/chains/nope", nil), "id", "nope") w := httptest.NewRecorder() h.HandleWidgetChainGet(w, req) if w.Code != http.StatusNotFound { t.Fatalf("status = %d, want 404", w.Code) } } func TestHandleWidgetChainsPauseResumeAbandon(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() h := &Handler{store: db} chain, err := h.store.CreateChain("Track", []models.ChainTaskInput{{Content: "Step 1"}, {Content: "Step 2"}}) if err != nil { t.Fatal(err) } pauseReq := withURLParam(httptest.NewRequest("POST", "/api/widget/chains/"+chain.ID+"/pause", nil), "id", chain.ID) w := httptest.NewRecorder() h.HandleWidgetChainsPause(w, pauseReq) if w.Code != http.StatusOK { t.Fatalf("pause status = %d, want 200", w.Code) } paused, err := h.store.GetChain(chain.ID) if err != nil || paused.Status != "paused" { t.Fatalf("chain after pause = %+v, err=%v", paused, err) } resumeReq := withURLParam(httptest.NewRequest("POST", "/api/widget/chains/"+chain.ID+"/resume", nil), "id", chain.ID) w = httptest.NewRecorder() h.HandleWidgetChainsResume(w, resumeReq) if w.Code != http.StatusOK { t.Fatalf("resume status = %d, want 200", w.Code) } resumed, err := h.store.GetChain(chain.ID) if err != nil || resumed.Status != "active" { t.Fatalf("chain after resume = %+v, err=%v", resumed, err) } abandonReq := withURLParam(httptest.NewRequest("POST", "/api/widget/chains/"+chain.ID+"/abandon", nil), "id", chain.ID) w = httptest.NewRecorder() h.HandleWidgetChainsAbandon(w, abandonReq) if w.Code != http.StatusOK { t.Fatalf("abandon status = %d, want 200", w.Code) } abandoned, err := h.store.GetChain(chain.ID) if err != nil || abandoned.Status != "abandoned" { t.Fatalf("chain after abandon = %+v, err=%v", abandoned, err) } } func TestHandleWidgetChainsPause_UnknownID_Returns404(t *testing.T) { db, cleanup := setupTestDB(t) defer cleanup() h := &Handler{store: db} req := withURLParam(httptest.NewRequest("POST", "/api/widget/chains/nope/pause", nil), "id", "nope") w := httptest.NewRecorder() h.HandleWidgetChainsPause(w, req) if w.Code != http.StatusNotFound { t.Fatalf("status = %d, want 404", w.Code) } }