test(09-04): add etape reorder regressions
This commit is contained in:
parent
be86eb7d80
commit
55263e4a52
1 changed files with 263 additions and 0 deletions
|
|
@ -688,6 +688,174 @@ func TestTaskReorderCrossColumn(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskReorderPreservesEtapeAssignment(t *testing.T) {
|
||||||
|
pool, cleanup := setupTestDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
q := sqlc.New(pool)
|
||||||
|
store := auth.NewStore(q)
|
||||||
|
router := newTaskTestRouter(q, store)
|
||||||
|
|
||||||
|
user := preInsertUser(t, ctx, q, "reorderpreserveetape@example.com", "correct-horse-12")
|
||||||
|
tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{
|
||||||
|
UserID: user.ID,
|
||||||
|
Title: "Reorder Preserve Etape",
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
task, err := q.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "Move Me",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Status: sqlc.TaskStatusTodo,
|
||||||
|
Position: 100,
|
||||||
|
EtapeID: pgtype.UUID{Bytes: etape.ID, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertTask: %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(), []*http.Cookie{sessionCookie})
|
||||||
|
|
||||||
|
form := url.Values{
|
||||||
|
"task_id": {task.ID.String()},
|
||||||
|
"status": {"done"},
|
||||||
|
"position": {"100"},
|
||||||
|
"_csrf": {csrfToken},
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/tasks/reorder", 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 reorder status = %d; want 200", rec.Code)
|
||||||
|
}
|
||||||
|
updated, err := q.GetTaskByID(ctx, sqlc.GetTaskByIDParams{ID: task.ID, TabloID: tablo.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTaskByID: %v", err)
|
||||||
|
}
|
||||||
|
if !updated.EtapeID.Valid || updated.EtapeID.Bytes != etape.ID {
|
||||||
|
t.Fatalf("etape assignment changed after reorder: %+v", updated.EtapeID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilteredTaskReorderPreservesHiddenTasks(t *testing.T) {
|
||||||
|
pool, cleanup := setupTestDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
q := sqlc.New(pool)
|
||||||
|
store := auth.NewStore(q)
|
||||||
|
router := newTaskTestRouter(q, store)
|
||||||
|
|
||||||
|
user := preInsertUser(t, ctx, q, "filteredreorderhidden@example.com", "correct-horse-12")
|
||||||
|
tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{
|
||||||
|
UserID: user.ID,
|
||||||
|
Title: "Filtered Reorder",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Color: pgtype.Text{Valid: false},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertTablo: %v", err)
|
||||||
|
}
|
||||||
|
firstEtape, err := q.InsertEtape(ctx, sqlc.InsertEtapeParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "Design",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Position: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertEtape first: %v", err)
|
||||||
|
}
|
||||||
|
secondEtape, err := q.InsertEtape(ctx, sqlc.InsertEtapeParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "Build",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Position: 200,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertEtape second: %v", err)
|
||||||
|
}
|
||||||
|
visible, err := q.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "Visible",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Status: sqlc.TaskStatusTodo,
|
||||||
|
Position: 100,
|
||||||
|
EtapeID: pgtype.UUID{Bytes: firstEtape.ID, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertTask visible: %v", err)
|
||||||
|
}
|
||||||
|
hidden, err := q.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "Hidden",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Status: sqlc.TaskStatusTodo,
|
||||||
|
Position: 200,
|
||||||
|
EtapeID: pgtype.UUID{Bytes: secondEtape.ID, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertTask hidden: %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?etape="+firstEtape.ID.String(), []*http.Cookie{sessionCookie})
|
||||||
|
|
||||||
|
form := url.Values{
|
||||||
|
"task_id": {visible.ID.String()},
|
||||||
|
"status": {"in_progress"},
|
||||||
|
"position": {"100"},
|
||||||
|
"_csrf": {csrfToken},
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/tasks/reorder?etape="+firstEtape.ID.String(), 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 filtered reorder status = %d; want 200", rec.Code)
|
||||||
|
}
|
||||||
|
hiddenAfter, err := q.GetTaskByID(ctx, sqlc.GetTaskByIDParams{ID: hidden.ID, TabloID: tablo.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTaskByID hidden: %v", err)
|
||||||
|
}
|
||||||
|
if !hiddenAfter.EtapeID.Valid || hiddenAfter.EtapeID.Bytes != secondEtape.ID {
|
||||||
|
t.Fatalf("hidden task etape changed: %+v", hiddenAfter.EtapeID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- TestTaskReorderSameColumn (TASK-05) ----
|
// ---- TestTaskReorderSameColumn (TASK-05) ----
|
||||||
|
|
||||||
// TestTaskReorderSameColumn verifies that POST /tablos/{id}/tasks/reorder
|
// TestTaskReorderSameColumn verifies that POST /tablos/{id}/tasks/reorder
|
||||||
|
|
@ -931,6 +1099,101 @@ func TestTaskOrderPersists(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskOrderPersistsWithEtapeFilter(t *testing.T) {
|
||||||
|
pool, cleanup := setupTestDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
q := sqlc.New(pool)
|
||||||
|
store := auth.NewStore(q)
|
||||||
|
router := newTaskTestRouter(q, store)
|
||||||
|
|
||||||
|
user := preInsertUser(t, ctx, q, "orderfilteretape@example.com", "correct-horse-12")
|
||||||
|
tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{
|
||||||
|
UserID: user.ID,
|
||||||
|
Title: "Order Filter",
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
first, err := q.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "First Filtered",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Status: sqlc.TaskStatusTodo,
|
||||||
|
Position: 100,
|
||||||
|
EtapeID: pgtype.UUID{Bytes: etape.ID, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertTask first: %v", err)
|
||||||
|
}
|
||||||
|
second, err := q.InsertTask(ctx, sqlc.InsertTaskParams{
|
||||||
|
TabloID: tablo.ID,
|
||||||
|
Title: "Second Filtered",
|
||||||
|
Description: pgtype.Text{Valid: false},
|
||||||
|
Status: sqlc.TaskStatusTodo,
|
||||||
|
Position: 200,
|
||||||
|
EtapeID: pgtype.UUID{Bytes: etape.ID, Valid: true},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("InsertTask second: %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?etape="+etape.ID.String(), []*http.Cookie{sessionCookie})
|
||||||
|
|
||||||
|
form := url.Values{
|
||||||
|
"task_id": {second.ID.String()},
|
||||||
|
"status": {"todo"},
|
||||||
|
"position": {"50"},
|
||||||
|
"_csrf": {csrfToken},
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/tasks/reorder?etape="+etape.ID.String(), 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("reorder status = %d; want 200", rec.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
getReq := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/tasks?etape="+etape.ID.String(), nil)
|
||||||
|
getReq.Header.Set("HX-Request", "true")
|
||||||
|
getReq.AddCookie(sessionCookie)
|
||||||
|
getRec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(getRec, getReq)
|
||||||
|
if getRec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("filtered GET status = %d; want 200", getRec.Code)
|
||||||
|
}
|
||||||
|
body := getRec.Body.String()
|
||||||
|
idxFirst := strings.Index(body, first.Title)
|
||||||
|
idxSecond := strings.Index(body, second.Title)
|
||||||
|
if idxFirst == -1 || idxSecond == -1 {
|
||||||
|
t.Fatalf("filtered body missing task titles; body: %.500s", body)
|
||||||
|
}
|
||||||
|
if idxSecond > idxFirst {
|
||||||
|
t.Fatalf("filtered order did not persist: second index=%d first index=%d", idxSecond, idxFirst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- TestTaskOwnership (T-04-IDOR) ----
|
// ---- TestTaskOwnership (T-04-IDOR) ----
|
||||||
|
|
||||||
// TestTaskOwnership verifies that GET and POST task routes accessed by a
|
// TestTaskOwnership verifies that GET and POST task routes accessed by a
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue