Files
bbq/handlers_test.go
T
ryan fe5cdd92a1 Dedupe RSVPs and add tests for auto-RSVP on claim
Add migration to clean up duplicate RSVPs (keep earliest per event+name).
Add GetRsvpByName and DeleteDuplicateRsvps queries. Tests cover auto-RSVP
creation, duplicate prevention, and case-insensitive matching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-18 22:55:21 -04:00

169 lines
4.2 KiB
Go

package main
import (
"context"
"database/sql"
"testing"
_ "github.com/mattn/go-sqlite3"
"github.com/ryanchen/bbq/db"
)
func setupTestDB(t *testing.T) (*sql.DB, *db.Queries) {
t.Helper()
database, err := sql.Open("sqlite3", ":memory:?_foreign_keys=on")
if err != nil {
t.Fatal(err)
}
if _, err := database.Exec(schemaSQL); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { database.Close() })
return database, db.New(database)
}
func createTestEvent(t *testing.T, q *db.Queries) db.Event {
t.Helper()
event, err := q.CreateEvent(context.Background(), db.CreateEventParams{
Slug: "test", Title: "Test Event", Date: "June 1", Time: "2pm",
Location: "Park", AdminToken: "tok123",
})
if err != nil {
t.Fatal(err)
}
return event
}
func TestAutoRsvpOnClaim(t *testing.T) {
_, q := setupTestDB(t)
ctx := context.Background()
event := createTestEvent(t, q)
slot, err := q.CreateSlot(ctx, db.CreateSlotParams{
EventID: event.ID, Name: "Drinks", MaxClaims: 5,
})
if err != nil {
t.Fatal(err)
}
// Claim a slot — should also create an RSVP
_, err = q.CreateClaim(ctx, db.CreateClaimParams{
SlotID: slot.ID, Name: "Alice",
})
if err != nil {
t.Fatal(err)
}
// Simulate the auto-RSVP logic from handleClaim
_, err = q.GetRsvpByName(ctx, db.GetRsvpByNameParams{
EventID: event.ID, Name: "Alice",
})
if err == sql.ErrNoRows {
_, err = q.CreateRsvp(ctx, db.CreateRsvpParams{
EventID: event.ID, Name: "Alice",
})
if err != nil {
t.Fatal(err)
}
}
rsvps, _ := q.ListRsvps(ctx, event.ID)
if len(rsvps) != 1 {
t.Fatalf("expected 1 RSVP, got %d", len(rsvps))
}
if rsvps[0].Name != "Alice" {
t.Fatalf("expected RSVP name Alice, got %s", rsvps[0].Name)
}
}
func TestAutoRsvpNoDuplicate(t *testing.T) {
_, q := setupTestDB(t)
ctx := context.Background()
event := createTestEvent(t, q)
slot, _ := q.CreateSlot(ctx, db.CreateSlotParams{
EventID: event.ID, Name: "Drinks", MaxClaims: 5,
})
// RSVP first
q.CreateRsvp(ctx, db.CreateRsvpParams{
EventID: event.ID, Name: "Bob",
})
// Then claim — auto-RSVP should skip
q.CreateClaim(ctx, db.CreateClaimParams{
SlotID: slot.ID, Name: "Bob",
})
_, err := q.GetRsvpByName(ctx, db.GetRsvpByNameParams{
EventID: event.ID, Name: "Bob",
})
if err == sql.ErrNoRows {
q.CreateRsvp(ctx, db.CreateRsvpParams{
EventID: event.ID, Name: "Bob",
})
}
rsvps, _ := q.ListRsvps(ctx, event.ID)
if len(rsvps) != 1 {
t.Fatalf("expected 1 RSVP (no duplicate), got %d", len(rsvps))
}
}
func TestAutoRsvpCaseInsensitive(t *testing.T) {
_, q := setupTestDB(t)
ctx := context.Background()
event := createTestEvent(t, q)
slot, _ := q.CreateSlot(ctx, db.CreateSlotParams{
EventID: event.ID, Name: "Drinks", MaxClaims: 5,
})
// RSVP as "alice"
q.CreateRsvp(ctx, db.CreateRsvpParams{
EventID: event.ID, Name: "alice",
})
// Claim as "Alice" — should not create duplicate
q.CreateClaim(ctx, db.CreateClaimParams{
SlotID: slot.ID, Name: "Alice",
})
_, err := q.GetRsvpByName(ctx, db.GetRsvpByNameParams{
EventID: event.ID, Name: "Alice",
})
if err == sql.ErrNoRows {
q.CreateRsvp(ctx, db.CreateRsvpParams{
EventID: event.ID, Name: "Alice",
})
}
rsvps, _ := q.ListRsvps(ctx, event.ID)
if len(rsvps) != 1 {
t.Fatalf("expected 1 RSVP (case-insensitive dedup), got %d", len(rsvps))
}
}
func TestDeduplicateExistingRsvps(t *testing.T) {
database, q := setupTestDB(t)
ctx := context.Background()
event := createTestEvent(t, q)
// Insert duplicate RSVPs directly
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "Charlie"})
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "Charlie"})
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "charlie"})
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "Dana"})
rsvps, _ := q.ListRsvps(ctx, event.ID)
if len(rsvps) != 4 {
t.Fatalf("expected 4 RSVPs before dedup, got %d", len(rsvps))
}
// Run the dedup query (same as migration)
database.Exec(`DELETE FROM rsvps WHERE id NOT IN (SELECT MIN(id) FROM rsvps GROUP BY event_id, name COLLATE NOCASE)`)
rsvps, _ = q.ListRsvps(ctx, event.ID)
if len(rsvps) != 2 {
t.Fatalf("expected 2 RSVPs after dedup (Charlie + Dana), got %d", len(rsvps))
}
}