diff --git a/backend/internal/web/handlers_discussion_test.go b/backend/internal/web/handlers_discussion_test.go new file mode 100644 index 0000000..799d242 --- /dev/null +++ b/backend/internal/web/handlers_discussion_test.go @@ -0,0 +1,287 @@ +package web + +import ( + "context" + "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/pgtype" + "github.com/jackc/pgx/v5/pgxpool" +) + +func newDiscussionTestRouter(q *sqlc.Queries, store *auth.Store) http.Handler { + authDeps := AuthDeps{Queries: q, Store: store, Secure: false} + tabloDeps := TablosDeps{Queries: q} + router, err := NewRouter(stubPinger{}, os.DirFS("./static"), authDeps, tabloDeps, TasksDeps{Queries: q}, EtapesDeps{Queries: q}, EventsDeps{Queries: q}, PlanningDeps{Queries: q}, FilesDeps{Queries: q}, testCSRFKey, "dev", "localhost") + if err != nil { + panic("newDiscussionTestRouter: " + err.Error()) + } + return router +} + +func insertDiscussionTestTablo(t *testing.T, ctx context.Context, q *sqlc.Queries, user sqlc.User, title string) sqlc.Tablo { + t.Helper() + tablo, err := q.InsertTablo(ctx, sqlc.InsertTabloParams{ + UserID: user.ID, + Title: title, + Description: pgtype.Text{Valid: false}, + Color: pgtype.Text{Valid: false}, + }) + if err != nil { + t.Fatalf("InsertTablo: %v", err) + } + return tablo +} + +func insertDiscussionTestMessage(t *testing.T, ctx context.Context, pool *pgxpool.Pool, q *sqlc.Queries, tabloID, authorID uuid.UUID, body string, createdAt time.Time) sqlc.DiscussionMessage { + t.Helper() + msg, err := q.CreateDiscussionMessage(ctx, sqlc.CreateDiscussionMessageParams{ + TabloID: tabloID, + AuthorUserID: authorID, + Body: body, + }) + if err != nil { + t.Fatalf("CreateDiscussionMessage: %v", err) + } + if _, err := pool.Exec(ctx, `UPDATE discussion_messages SET created_at = $1, updated_at = $1 WHERE id = $2`, createdAt, msg.ID); err != nil { + t.Fatalf("set discussion message created_at: %v", err) + } + msg.CreatedAt = pgtype.Timestamptz{Time: createdAt, Valid: true} + msg.UpdatedAt = pgtype.Timestamptz{Time: createdAt, Valid: true} + return msg +} + +func TestDiscussionTabRendersHistoryAndComposer(t *testing.T) { + pool, cleanup := setupTestDB(t) + defer cleanup() + + ctx := context.Background() + q := sqlc.New(pool) + store := auth.NewStore(q) + router := newDiscussionTestRouter(q, store) + + user := preInsertUser(t, ctx, q, "discussion-history@example.com", "correct-horse-12") + tablo := insertDiscussionTestTablo(t, ctx, q, user, "Discussion History Tablo") + first := insertDiscussionTestMessage(t, ctx, pool, q, tablo.ID, user.ID, " first", time.Date(2026, 5, 16, 9, 30, 0, 0, time.UTC)) + second := insertDiscussionTestMessage(t, ctx, pool, q, tablo.ID, user.ID, "Second message", time.Date(2026, 5, 17, 10, 45, 0, 0, time.UTC)) + sessionCookie := sessionCookieForUser(t, ctx, store, user) + + req := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/discussion", 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 discussion tab status = %d; want 200; body: %.500s", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + for _, want := range []string{"Discussion", "1 participant", user.Email, "May 16, 2026", "May 17, 2026", "Message", "Write a message...", "Send message"} { + if !strings.Contains(body, want) { + t.Errorf("discussion tab missing %q; body: %.1000s", want, body) + } + } + if strings.Contains(body, "