diff options
Diffstat (limited to 'internal/handlers/chains_test.go')
| -rw-r--r-- | internal/handlers/chains_test.go | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/internal/handlers/chains_test.go b/internal/handlers/chains_test.go new file mode 100644 index 0000000..0ebcc7a --- /dev/null +++ b/internal/handlers/chains_test.go @@ -0,0 +1,161 @@ +package handlers + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/go-chi/chi/v5" +) + +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":["Study Technician","Pass exam"]}` + 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", []string{"Step 1", "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", []string{"Step 1", "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) + } +} |
