From a8a3e5f596d44a23d90bc13ef0eaafbda4fb82af Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Fri, 15 May 2026 22:33:40 +0200 Subject: [PATCH] test(09-01): add failing etape slice tests --- backend/internal/web/handlers_etapes_test.go | 225 +++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 backend/internal/web/handlers_etapes_test.go diff --git a/backend/internal/web/handlers_etapes_test.go b/backend/internal/web/handlers_etapes_test.go new file mode 100644 index 0000000..62680c9 --- /dev/null +++ b/backend/internal/web/handlers_etapes_test.go @@ -0,0 +1,225 @@ +package web + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + + "backend/internal/auth" + "backend/internal/db/sqlc" + + "github.com/jackc/pgx/v5/pgtype" +) + +func newEtapeTestRouter(q *sqlc.Queries, store *auth.Store) http.Handler { + authDeps := AuthDeps{Queries: q, Store: store, Secure: false} + tabloDeps := TablosDeps{Queries: q} + taskDeps := TasksDeps{Queries: q} + etapeDeps := EtapesDeps{Queries: q} + router, err := NewRouter(stubPinger{}, os.DirFS("./static"), authDeps, tabloDeps, taskDeps, etapeDeps, FilesDeps{Queries: q}, testCSRFKey, "dev", "localhost") + if err != nil { + panic("newEtapeTestRouter: " + err.Error()) + } + return router +} + +func TestEtapeCreateRendersChipAndCount(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEtapeTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "etapecreate@example.com", "correct-horse-12") + tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{ + UserID: user.ID, + Title: "Etape Create Tablo", + Description: pgtype.Text{Valid: false}, + Color: pgtype.Text{Valid: false}, + }) + if err != nil { + t.Fatalf("InsertTablo: %v", err) + } + + cookieVal, _, err := store.Create(ctx, user.ID) + if err != nil { + t.Fatalf("store.Create: %v", err) + } + sessionCookie := &http.Cookie{Name: auth.SessionCookieName, Value: cookieVal} + csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/tasks", []*http.Cookie{sessionCookie}) + + form := url.Values{ + "title": {"Design"}, + "_csrf": {csrfToken}, + } + req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/etapes", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("HX-Request", "true") + for _, c := range csrfCookies { + req.AddCookie(c) + } + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("POST /tablos/{id}/etapes status = %d; want 200", rec.Code) + } + body := rec.Body.String() + for _, want := range []string{"Design", "Unassigned", "All"} { + if !strings.Contains(body, want) { + t.Errorf("response body missing %q; body: %.500s", want, body) + } + } +} + +func TestTaskCreateAssignsEtape(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEtapeTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "tasketapecreate@example.com", "correct-horse-12") + tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{ + UserID: user.ID, + Title: "Task Etape Create Tablo", + Description: pgtype.Text{Valid: false}, + Color: pgtype.Text{Valid: false}, + }) + if err != nil { + t.Fatalf("InsertTablo: %v", err) + } + etape, err := q.InsertEtape(ctx, sqlc.InsertEtapeParams{ + TabloID: tablo.ID, + Title: "Design", + Description: pgtype.Text{Valid: false}, + Position: 100, + }) + if err != nil { + t.Fatalf("InsertEtape: %v", err) + } + + cookieVal, _, err := store.Create(ctx, user.ID) + if err != nil { + t.Fatalf("store.Create: %v", err) + } + sessionCookie := &http.Cookie{Name: auth.SessionCookieName, Value: cookieVal} + csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/tasks", []*http.Cookie{sessionCookie}) + + form := url.Values{ + "title": {"Assigned Task"}, + "status": {"todo"}, + "etape_id": {etape.ID.String()}, + "_csrf": {csrfToken}, + } + req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/tasks", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("HX-Request", "true") + for _, c := range csrfCookies { + req.AddCookie(c) + } + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("POST /tablos/{id}/tasks status = %d; want 200", rec.Code) + } + tasks, err := q.ListTasksByTablo(ctx, tablo.ID) + if err != nil { + t.Fatalf("ListTasksByTablo: %v", err) + } + if len(tasks) != 1 { + t.Fatalf("task count = %d; want 1", len(tasks)) + } + if !tasks[0].EtapeID.Valid || tasks[0].EtapeID.Bytes != etape.ID { + t.Fatalf("task etape_id = %+v; want %s", tasks[0].EtapeID, etape.ID) + } +} + +func TestEtapeFilterRendersExistingKanbanColumns(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newEtapeTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "etapefilter@example.com", "correct-horse-12") + tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{ + UserID: user.ID, + Title: "Etape Filter Tablo", + Description: pgtype.Text{Valid: false}, + Color: pgtype.Text{Valid: false}, + }) + if err != nil { + t.Fatalf("InsertTablo: %v", err) + } + etape, err := q.InsertEtape(ctx, sqlc.InsertEtapeParams{ + TabloID: tablo.ID, + Title: "Design", + Description: pgtype.Text{Valid: false}, + Position: 100, + }) + if err != nil { + t.Fatalf("InsertEtape: %v", err) + } + _, err = q.InsertTask(ctx, sqlc.InsertTaskParams{ + TabloID: tablo.ID, + Title: "Assigned Task", + Description: pgtype.Text{Valid: false}, + Status: sqlc.TaskStatusTodo, + Position: 100, + EtapeID: pgtype.UUID{Bytes: etape.ID, Valid: true}, + }) + if err != nil { + t.Fatalf("InsertTask assigned: %v", err) + } + _, err = q.InsertTask(ctx, sqlc.InsertTaskParams{ + TabloID: tablo.ID, + Title: "Unassigned Task", + Description: pgtype.Text{Valid: false}, + Status: sqlc.TaskStatusTodo, + Position: 200, + EtapeID: pgtype.UUID{Valid: false}, + }) + if err != nil { + t.Fatalf("InsertTask unassigned: %v", err) + } + + cookieVal, _, err := store.Create(ctx, user.ID) + if err != nil { + t.Fatalf("store.Create: %v", err) + } + sessionCookie := &http.Cookie{Name: auth.SessionCookieName, Value: cookieVal} + + req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/tasks?etape="+etape.ID.String(), nil) + req.Header.Set("HX-Request", "true") + req.AddCookie(sessionCookie) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("GET /tablos/{id}/tasks?etape status = %d; want 200", rec.Code) + } + body := rec.Body.String() + for _, col := range []string{"To do", "In progress", "In review", "Done"} { + if !strings.Contains(body, col) { + t.Errorf("filtered kanban missing column header %q", col) + } + } + if !strings.Contains(body, "Assigned Task") { + t.Errorf("filtered body missing assigned task; body: %.500s", body) + } + if strings.Contains(body, "Unassigned Task") { + t.Errorf("filtered body includes unassigned task; body: %.500s", body) + } +}