test(12-02): add discussion unread coverage

This commit is contained in:
Arthur Belleville 2026-05-16 10:12:56 +02:00
parent 6af4d70d51
commit e3c8d51782
No known key found for this signature in database

View file

@ -285,3 +285,161 @@ func TestDiscussionRequiresCSRF(t *testing.T) {
t.Fatalf("POST discussion without CSRF succeeded; want rejection")
}
}
func TestTablosListDiscussionUnreadBadge(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-unread@example.com", "correct-horse-12")
unreadTablo := insertDiscussionTestTablo(t, ctx, q, user, "Unread Tablo")
quietTablo := insertDiscussionTestTablo(t, ctx, q, user, "Quiet Tablo")
for i := 0; i < 3; i++ {
insertDiscussionTestMessage(t, ctx, pool, q, unreadTablo.ID, user.ID, "Unread message", time.Date(2026, 5, 16, 9, i, 0, 0, time.UTC))
}
sessionCookie := sessionCookieForUser(t, ctx, store, user)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET / status = %d; want 200", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, `aria-label="3 unread discussion messages"`) {
t.Fatalf("unread badge accessible label missing; body: %.1200s", body)
}
unreadCard := discussionTestTabloCardHTML(t, body, unreadTablo.ID)
if !strings.Contains(unreadCard, ">3<") {
t.Fatalf("unread tablo card missing numeric badge; card: %.800s", unreadCard)
}
quietCard := discussionTestTabloCardHTML(t, body, quietTablo.ID)
if strings.Contains(quietCard, "unread discussion messages") {
t.Fatalf("quiet tablo card unexpectedly contains unread badge; card: %.800s", quietCard)
}
}
func TestTablosListDiscussionUnreadDoesNotLeakOtherUsers(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-unread-owner@example.com", "correct-horse-12")
other := preInsertUser(t, ctx, q, "discussion-unread-other@example.com", "correct-horse-12")
_ = insertDiscussionTestTablo(t, ctx, q, user, "Owner Quiet Tablo")
otherTablo := insertDiscussionTestTablo(t, ctx, q, other, "Other Private Tablo")
insertDiscussionTestMessage(t, ctx, pool, q, otherTablo.ID, other.ID, "Other user's message", time.Date(2026, 5, 16, 9, 0, 0, 0, time.UTC))
sessionCookie := sessionCookieForUser(t, ctx, store, user)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET / status = %d; want 200", rec.Code)
}
body := rec.Body.String()
if strings.Contains(body, "Other Private Tablo") || strings.Contains(body, "unread discussion messages") {
t.Fatalf("dashboard leaked another user's unread state; body: %.1200s", body)
}
}
func TestDiscussionGetMarksMessagesRead(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-read@example.com", "correct-horse-12")
tablo := insertDiscussionTestTablo(t, ctx, q, user, "Read State Tablo")
insertDiscussionTestMessage(t, ctx, pool, q, tablo.ID, user.ID, "First unread", time.Date(2026, 5, 16, 9, 0, 0, 0, time.UTC))
insertDiscussionTestMessage(t, ctx, pool, q, tablo.ID, user.ID, "Second unread", time.Date(2026, 5, 16, 10, 0, 0, 0, time.UTC))
sessionCookie := sessionCookieForUser(t, ctx, store, user)
beforeReq := httptest.NewRequest(http.MethodGet, "/", nil)
beforeReq.AddCookie(sessionCookie)
beforeRec := httptest.NewRecorder()
router.ServeHTTP(beforeRec, beforeReq)
if !strings.Contains(beforeRec.Body.String(), `aria-label="2 unread discussion messages"`) {
t.Fatalf("pre-read dashboard missing unread badge; body: %.1200s", beforeRec.Body.String())
}
readReq := httptest.NewRequest(http.MethodGet, "/tablos/"+tablo.ID.String()+"/discussion", nil)
readReq.AddCookie(sessionCookie)
readRec := httptest.NewRecorder()
router.ServeHTTP(readRec, readReq)
if readRec.Code != http.StatusOK {
t.Fatalf("GET discussion status = %d; want 200", readRec.Code)
}
afterReq := httptest.NewRequest(http.MethodGet, "/", nil)
afterReq.AddCookie(sessionCookie)
afterRec := httptest.NewRecorder()
router.ServeHTTP(afterRec, afterReq)
if strings.Contains(afterRec.Body.String(), "unread discussion messages") {
t.Fatalf("post-read dashboard still shows unread badge; body: %.1200s", afterRec.Body.String())
}
}
func TestDiscussionPostMarksSenderRead(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-sender-read@example.com", "correct-horse-12")
tablo := insertDiscussionTestTablo(t, ctx, q, user, "Sender Read Tablo")
sessionCookie := sessionCookieForUser(t, ctx, store, user)
csrfToken, csrfCookies := getCSRFToken(t, router, "/tablos/"+tablo.ID.String()+"/discussion", []*http.Cookie{sessionCookie})
form := url.Values{"body": {"Sender-created message"}, "_csrf": {csrfToken}}
req := httptest.NewRequest(http.MethodPost, "/tablos/"+tablo.ID.String()+"/discussion/messages", 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 discussion message status = %d; want 200; body: %.500s", rec.Code, rec.Body.String())
}
dashboardReq := httptest.NewRequest(http.MethodGet, "/", nil)
dashboardReq.AddCookie(sessionCookie)
dashboardRec := httptest.NewRecorder()
router.ServeHTTP(dashboardRec, dashboardReq)
if strings.Contains(dashboardRec.Body.String(), "unread discussion messages") {
t.Fatalf("sender's own message counted unread; body: %.1200s", dashboardRec.Body.String())
}
}
func discussionTestTabloCardHTML(t *testing.T, body string, id uuid.UUID) string {
t.Helper()
start := strings.Index(body, `id="tablo-`+id.String()+`"`)
if start == -1 {
t.Fatalf("tablo card %s not found; body: %.1200s", id, body)
}
next := strings.Index(body[start+1:], `id="tablo-`)
if next == -1 {
return body[start:]
}
return body[start : start+1+next]
}