test(10-02): add failing event mutation tests
This commit is contained in:
parent
c45e96d610
commit
e5f083d2a8
1 changed files with 263 additions and 0 deletions
|
|
@ -2,16 +2,20 @@ package web
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"backend/internal/auth"
|
||||
"backend/internal/db/sqlc"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
|
|
@ -51,6 +55,46 @@ func sessionCookieForUser(t *testing.T, ctx context.Context, store *auth.Store,
|
|||
return &http.Cookie{Name: auth.SessionCookieName, Value: cookieVal}
|
||||
}
|
||||
|
||||
func eventTestDate(t *testing.T, raw string) pgtype.Date {
|
||||
t.Helper()
|
||||
parsed, err := time.Parse("2006-01-02", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("eventTestDate(%q): %v", raw, err)
|
||||
}
|
||||
return pgtype.Date{Time: parsed, Valid: true}
|
||||
}
|
||||
|
||||
func eventTestTime(t *testing.T, raw string) pgtype.Time {
|
||||
t.Helper()
|
||||
parsed, err := time.Parse("15:04", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("eventTestTime(%q): %v", raw, err)
|
||||
}
|
||||
micros := int64(parsed.Hour())*int64(time.Hour/time.Microsecond) + int64(parsed.Minute())*int64(time.Minute/time.Microsecond)
|
||||
return pgtype.Time{Microseconds: micros, Valid: true}
|
||||
}
|
||||
|
||||
func insertEventTestEvent(t *testing.T, ctx context.Context, q *sqlc.Queries, tabloID uuid.UUID, title, date, start, end, location, description string) sqlc.Event {
|
||||
t.Helper()
|
||||
var endTime pgtype.Time
|
||||
if end != "" {
|
||||
endTime = eventTestTime(t, end)
|
||||
}
|
||||
event, err := q.CreateEvent(ctx, sqlc.CreateEventParams{
|
||||
TabloID: tabloID,
|
||||
Title: title,
|
||||
EventDate: eventTestDate(t, date),
|
||||
StartTime: eventTestTime(t, start),
|
||||
EndTime: endTime,
|
||||
Description: pgtype.Text{String: description, Valid: description != ""},
|
||||
Location: pgtype.Text{String: location, Valid: location != ""},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateEvent: %v", err)
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
func TestEventsTabRendersMonthGrid(t *testing.T) {
|
||||
pool, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
|
@ -182,3 +226,222 @@ func TestEventsTabOwnershipReturns404(t *testing.T) {
|
|||
t.Fatalf("non-owner GET /tablos/{id}/events status = %d; want 404", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventEditRendersInlineForm(t *testing.T) {
|
||||
pool, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
q := sqlc.New(pool)
|
||||
store := auth.NewStore(q)
|
||||
router := newEventTestRouter(q, store)
|
||||
|
||||
user := preInsertUser(t, ctx, q, "eventedit@example.com", "correct-horse-12")
|
||||
tablo := insertEventTestTablo(t, ctx, q, user, "Event Edit Tablo")
|
||||
event := insertEventTestEvent(t, ctx, q, tablo.ID, "Planning Review", "2026-05-21", "09:30", "10:30", "Studio", "Discuss schedule")
|
||||
sessionCookie := sessionCookieForUser(t, ctx, store, user)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events/"+event.ID.String()+"/edit?month=2026-05", 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 event edit status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{"Save event changes", "Planning Review", "2026-05-21", "09:30", "Studio", "Discuss schedule"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("edit form missing %q; body: %.800s", want, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventUpdateChangesCalendarPlacement(t *testing.T) {
|
||||
pool, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
q := sqlc.New(pool)
|
||||
store := auth.NewStore(q)
|
||||
router := newEventTestRouter(q, store)
|
||||
|
||||
user := preInsertUser(t, ctx, q, "eventupdate@example.com", "correct-horse-12")
|
||||
tablo := insertEventTestTablo(t, ctx, q, user, "Event Update Tablo")
|
||||
event := insertEventTestEvent(t, ctx, q, tablo.ID, "Original Review", "2026-05-21", "09:30", "", "Studio", "Discuss schedule")
|
||||
sessionCookie := sessionCookieForUser(t, ctx, store, user)
|
||||
csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", []*http.Cookie{sessionCookie})
|
||||
|
||||
form := url.Values{
|
||||
"title": {"Moved Review"},
|
||||
"event_date": {"2026-05-24"},
|
||||
"start_time": {"14:15"},
|
||||
"end_time": {"15:00"},
|
||||
"location": {"HQ"},
|
||||
"description": {"Updated details"},
|
||||
"_csrf": {csrfToken},
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/events/"+event.ID.String()+"?month=2026-05", 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 event update status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "Moved Review") {
|
||||
t.Fatalf("updated event title missing from refreshed calendar; body: %.800s", body)
|
||||
}
|
||||
if strings.Contains(body, "Original Review") {
|
||||
t.Fatalf("old event title still present after update; body: %.800s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventUpdateRejectsInvalidEndTime(t *testing.T) {
|
||||
pool, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
q := sqlc.New(pool)
|
||||
store := auth.NewStore(q)
|
||||
router := newEventTestRouter(q, store)
|
||||
|
||||
user := preInsertUser(t, ctx, q, "eventupdateinvalid@example.com", "correct-horse-12")
|
||||
tablo := insertEventTestTablo(t, ctx, q, user, "Event Update Invalid Tablo")
|
||||
event := insertEventTestEvent(t, ctx, q, tablo.ID, "Invalid Update", "2026-05-21", "09:30", "", "", "")
|
||||
sessionCookie := sessionCookieForUser(t, ctx, store, user)
|
||||
csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", []*http.Cookie{sessionCookie})
|
||||
|
||||
form := url.Values{
|
||||
"title": {"Invalid Update"},
|
||||
"event_date": {"2026-05-21"},
|
||||
"start_time": {"10:00"},
|
||||
"end_time": {"10:00"},
|
||||
"_csrf": {csrfToken},
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/events/"+event.ID.String()+"?month=2026-05", 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.StatusUnprocessableEntity {
|
||||
t.Fatalf("invalid event update status = %d; want 422", rec.Code)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "End time must be after the start time.") {
|
||||
t.Fatalf("validation copy missing; body: %.800s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventDeleteRemovesFromCalendarAndDatabase(t *testing.T) {
|
||||
pool, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
q := sqlc.New(pool)
|
||||
store := auth.NewStore(q)
|
||||
router := newEventTestRouter(q, store)
|
||||
|
||||
user := preInsertUser(t, ctx, q, "eventdelete@example.com", "correct-horse-12")
|
||||
tablo := insertEventTestTablo(t, ctx, q, user, "Event Delete Tablo")
|
||||
event := insertEventTestEvent(t, ctx, q, tablo.ID, "Delete Me", "2026-05-21", "09:30", "", "", "")
|
||||
sessionCookie := sessionCookieForUser(t, ctx, store, user)
|
||||
csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/events?month=2026-05", []*http.Cookie{sessionCookie})
|
||||
|
||||
confirmReq := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/events/"+event.ID.String()+"/delete-confirm?month=2026-05", nil)
|
||||
confirmReq.Header.Set("HX-Request", "true")
|
||||
confirmReq.AddCookie(sessionCookie)
|
||||
confirmRec := httptest.NewRecorder()
|
||||
router.ServeHTTP(confirmRec, confirmReq)
|
||||
if confirmRec.Code != http.StatusOK {
|
||||
t.Fatalf("GET delete confirm status = %d; want 200; body: %.500s", confirmRec.Code, confirmRec.Body.String())
|
||||
}
|
||||
if !strings.Contains(confirmRec.Body.String(), "Delete event?") {
|
||||
t.Fatalf("delete confirmation copy missing; body: %.800s", confirmRec.Body.String())
|
||||
}
|
||||
|
||||
form := url.Values{"_csrf": {csrfToken}}
|
||||
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/events/"+event.ID.String()+"/delete?month=2026-05", 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 event delete status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if strings.Contains(rec.Body.String(), "Delete Me") {
|
||||
t.Fatalf("deleted event title still present in refreshed calendar; body: %.800s", rec.Body.String())
|
||||
}
|
||||
_, err := q.GetEventByID(ctx, sqlc.GetEventByIDParams{ID: event.ID, TabloID: tablo.ID})
|
||||
if !errors.Is(err, pgx.ErrNoRows) {
|
||||
t.Fatalf("GetEventByID after delete err = %v; want pgx.ErrNoRows", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventMutationOwnershipReturns404(t *testing.T) {
|
||||
pool, cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
q := sqlc.New(pool)
|
||||
store := auth.NewStore(q)
|
||||
router := newEventTestRouter(q, store)
|
||||
|
||||
owner := preInsertUser(t, ctx, q, "eventmutationowner@example.com", "correct-horse-12")
|
||||
nonOwner := preInsertUser(t, ctx, q, "eventmutationnonowner@example.com", "correct-horse-12")
|
||||
tablo := insertEventTestTablo(t, ctx, q, owner, "Private Mutation Tablo")
|
||||
event := insertEventTestEvent(t, ctx, q, tablo.ID, "Private Event", "2026-05-21", "09:30", "", "", "")
|
||||
nonOwnerCookie := sessionCookieForUser(t, ctx, store, nonOwner)
|
||||
csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/new", []*http.Cookie{nonOwnerCookie})
|
||||
|
||||
getPaths := []string{
|
||||
"/tablos/" + tablo.ID.String() + "/events/" + event.ID.String() + "/edit?month=2026-05",
|
||||
"/tablos/" + tablo.ID.String() + "/events/" + event.ID.String() + "/delete-confirm?month=2026-05",
|
||||
}
|
||||
for _, path := range getPaths {
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
req.AddCookie(nonOwnerCookie)
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("non-owner GET %s status = %d; want 404", path, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
postBodies := map[string]url.Values{
|
||||
"/tablos/" + tablo.ID.String() + "/events/" + event.ID.String() + "?month=2026-05": {
|
||||
"title": {"Private Event"},
|
||||
"event_date": {"2026-05-21"},
|
||||
"start_time": {"09:30"},
|
||||
"_csrf": {csrfToken},
|
||||
},
|
||||
"/tablos/" + tablo.ID.String() + "/events/" + event.ID.String() + "/delete?month=2026-05": {
|
||||
"_csrf": {csrfToken},
|
||||
},
|
||||
}
|
||||
for path, form := range postBodies {
|
||||
req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
for _, c := range csrfCookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("non-owner POST %s status = %d; want 404", path, rec.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue