Add attendee cap with required login for capped events
Hosts can optionally cap total attendance (RSVPs + plus-ones) when creating an event. Capped events require guests to log in via the existing email code flow; RSVPs are tied to accounts and deduped per user. The event page shows spots left and replaces the form with a full notice when the cap is reached; the server rejects over-cap RSVPs and edits with 409 as a race backstop. Also threads a safeNext-validated ?next= param through the login and name-setup flow so guests land back on the event after signing in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -215,6 +215,91 @@ func TestUpdateRsvp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountGoing(t *testing.T) {
|
||||
_, q := setupTestDB(t)
|
||||
ctx := context.Background()
|
||||
event := createTestEvent(t, q)
|
||||
|
||||
going, err := q.CountGoing(ctx, event.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if going != 0 {
|
||||
t.Fatalf("expected 0 going on empty event, got %d", going)
|
||||
}
|
||||
|
||||
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "Alice", PlusOne: 0})
|
||||
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "Bob", PlusOne: 2})
|
||||
|
||||
going, err = q.CountGoing(ctx, event.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if going != 4 {
|
||||
t.Fatalf("expected 4 going (2 RSVPs + 2 plus-ones), got %d", going)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRsvpByUser(t *testing.T) {
|
||||
_, q := setupTestDB(t)
|
||||
ctx := context.Background()
|
||||
event := createTestEvent(t, q)
|
||||
|
||||
user, err := q.CreateUserByEmail(ctx, sql.NullString{String: "alice@example.com", Valid: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = q.GetRsvpByUser(ctx, db.GetRsvpByUserParams{
|
||||
EventID: event.ID, UserID: sql.NullInt64{Int64: user.ID, Valid: true},
|
||||
})
|
||||
if err != sql.ErrNoRows {
|
||||
t.Fatalf("expected ErrNoRows before RSVP, got %v", err)
|
||||
}
|
||||
|
||||
q.CreateRsvp(ctx, db.CreateRsvpParams{
|
||||
EventID: event.ID, Name: "Alice", PlusOne: 1,
|
||||
UserID: sql.NullInt64{Int64: user.ID, Valid: true},
|
||||
})
|
||||
|
||||
rsvp, err := q.GetRsvpByUser(ctx, db.GetRsvpByUserParams{
|
||||
EventID: event.ID, UserID: sql.NullInt64{Int64: user.ID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rsvp.Name != "Alice" {
|
||||
t.Fatalf("expected RSVP name Alice, got %s", rsvp.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttendeeCapArithmetic(t *testing.T) {
|
||||
_, q := setupTestDB(t)
|
||||
ctx := context.Background()
|
||||
event, err := q.CreateEvent(ctx, db.CreateEventParams{
|
||||
Slug: "capped", Title: "Capped Event", Date: "June 1", Time: "2pm",
|
||||
Location: "Park", AdminToken: "tok456", AttendeeCap: 3,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if event.AttendeeCap != 3 {
|
||||
t.Fatalf("expected AttendeeCap=3, got %d", event.AttendeeCap)
|
||||
}
|
||||
|
||||
q.CreateRsvp(ctx, db.CreateRsvpParams{EventID: event.ID, Name: "Alice", PlusOne: 1})
|
||||
|
||||
going, _ := q.CountGoing(ctx, event.ID)
|
||||
// Same check as handleRsvp: a new RSVP with plus_one=1 would exceed the cap.
|
||||
if going+1+1 <= event.AttendeeCap {
|
||||
t.Fatalf("expected RSVP +1 to exceed cap (going=%d, cap=%d)", going, event.AttendeeCap)
|
||||
}
|
||||
// A solo RSVP still fits.
|
||||
if going+1+0 > event.AttendeeCap {
|
||||
t.Fatalf("expected solo RSVP to fit (going=%d, cap=%d)", going, event.AttendeeCap)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRsvpPlusOne(t *testing.T) {
|
||||
_, q := setupTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user