feat: implement Story 4.3 - Returning Participant Detection
Prevent duplicate registrations for the same exchange: - Check for existing participant with same email (case-insensitive) - Show friendly message if already registered - Offer link to request new magic link for access - Allow same email to register for different exchanges - Comprehensive test suite with 4 tests All tests passing (90 total), 91% coverage maintained. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,26 @@ def register(slug: str):
|
||||
# If explicitly unchecked, it will be False
|
||||
reminder_enabled = form.reminder_enabled.data
|
||||
|
||||
# Check if participant with this email already exists for this exchange
|
||||
existing_participant = (
|
||||
db.session.query(Participant)
|
||||
.filter_by(exchange_id=exchange.id, email=email)
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing_participant:
|
||||
# Participant already registered
|
||||
flash(
|
||||
"You're already registered for this exchange. "
|
||||
"If you need access, you can request a new magic link below.",
|
||||
"info",
|
||||
)
|
||||
return render_template(
|
||||
"participant/register.html",
|
||||
exchange=exchange,
|
||||
form=form,
|
||||
)
|
||||
|
||||
# Create participant record
|
||||
participant = Participant(
|
||||
exchange_id=exchange.id,
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
"""Tests for Story 4.3: Returning Participant Detection.
|
||||
|
||||
As a potential participant who has already registered, I want to be notified
|
||||
if I try to register again so that I don't create duplicate entries.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from src.models.exchange import Exchange
|
||||
from src.models.participant import Participant
|
||||
|
||||
|
||||
class TestStory43ReturningParticipantDetection:
|
||||
"""Test suite for Story 4.3: Returning Participant Detection."""
|
||||
|
||||
def test_duplicate_email_shows_message(self, client, db):
|
||||
"""Test that registering with existing email shows message."""
|
||||
future_close_date = datetime.utcnow() + timedelta(days=7)
|
||||
future_exchange_date = datetime.utcnow() + timedelta(days=14)
|
||||
|
||||
exchange = Exchange(
|
||||
slug="winter2025",
|
||||
name="Office Secret Santa 2025",
|
||||
budget="$50",
|
||||
max_participants=10,
|
||||
registration_close_date=future_close_date,
|
||||
exchange_date=future_exchange_date,
|
||||
timezone="America/New_York",
|
||||
state=Exchange.STATE_REGISTRATION_OPEN,
|
||||
)
|
||||
db.session.add(exchange)
|
||||
db.session.flush()
|
||||
|
||||
# Create existing participant
|
||||
existing_participant = Participant(
|
||||
exchange_id=exchange.id,
|
||||
name="John Doe",
|
||||
email="john.doe@example.com",
|
||||
gift_ideas="Books",
|
||||
reminder_enabled=True,
|
||||
)
|
||||
db.session.add(existing_participant)
|
||||
db.session.commit()
|
||||
|
||||
# Try to register again with same email (case insensitive)
|
||||
response = client.post(
|
||||
"/exchange/winter2025/register",
|
||||
data={
|
||||
"name": "John Doe",
|
||||
"email": "John.Doe@Example.COM", # Different case
|
||||
"gift_ideas": "Gadgets",
|
||||
},
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
# Should show message that user is already registered
|
||||
assert response.status_code == 200
|
||||
assert b"already registered" in response.data
|
||||
|
||||
# Should not create duplicate participant
|
||||
participants = (
|
||||
db.session.query(Participant).filter_by(email="john.doe@example.com").all()
|
||||
)
|
||||
assert len(participants) == 1
|
||||
|
||||
def test_duplicate_detection_offers_magic_link_request(self, client, db):
|
||||
"""Test that duplicate detection page offers link to request magic link."""
|
||||
future_close_date = datetime.utcnow() + timedelta(days=7)
|
||||
future_exchange_date = datetime.utcnow() + timedelta(days=14)
|
||||
|
||||
exchange = Exchange(
|
||||
slug="test123",
|
||||
name="Test Exchange",
|
||||
budget="$30",
|
||||
max_participants=10,
|
||||
registration_close_date=future_close_date,
|
||||
exchange_date=future_exchange_date,
|
||||
timezone="America/New_York",
|
||||
state=Exchange.STATE_REGISTRATION_OPEN,
|
||||
)
|
||||
db.session.add(exchange)
|
||||
db.session.flush()
|
||||
|
||||
# Create existing participant
|
||||
participant = Participant(
|
||||
exchange_id=exchange.id,
|
||||
name="Jane Smith",
|
||||
email="jane@example.com",
|
||||
reminder_enabled=True,
|
||||
)
|
||||
db.session.add(participant)
|
||||
db.session.commit()
|
||||
|
||||
# Try to register again
|
||||
response = client.post(
|
||||
"/exchange/test123/register",
|
||||
data={
|
||||
"name": "Jane Smith",
|
||||
"email": "jane@example.com",
|
||||
},
|
||||
)
|
||||
|
||||
# Should show link to request access
|
||||
assert (
|
||||
b"request a new magic link" in response.data
|
||||
or b"request access" in response.data
|
||||
)
|
||||
|
||||
def test_different_emails_can_register(self, client, db):
|
||||
"""Test that different emails can register successfully."""
|
||||
future_close_date = datetime.utcnow() + timedelta(days=7)
|
||||
future_exchange_date = datetime.utcnow() + timedelta(days=14)
|
||||
|
||||
exchange = Exchange(
|
||||
slug="test123",
|
||||
name="Test Exchange",
|
||||
budget="$30",
|
||||
max_participants=10,
|
||||
registration_close_date=future_close_date,
|
||||
exchange_date=future_exchange_date,
|
||||
timezone="America/New_York",
|
||||
state=Exchange.STATE_REGISTRATION_OPEN,
|
||||
)
|
||||
db.session.add(exchange)
|
||||
db.session.flush()
|
||||
|
||||
# Register first user
|
||||
participant1 = Participant(
|
||||
exchange_id=exchange.id,
|
||||
name="Alice",
|
||||
email="alice@example.com",
|
||||
reminder_enabled=True,
|
||||
)
|
||||
db.session.add(participant1)
|
||||
db.session.commit()
|
||||
|
||||
# Register second user with different email (should succeed)
|
||||
response = client.post(
|
||||
"/exchange/test123/register",
|
||||
data={
|
||||
"name": "Bob",
|
||||
"email": "bob@example.com",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
# Should redirect to success page
|
||||
assert response.status_code == 302
|
||||
assert "/exchange/test123/register/success" in response.location
|
||||
|
||||
# Should have two participants
|
||||
participants = db.session.query(Participant).all()
|
||||
assert len(participants) == 2
|
||||
|
||||
def test_same_email_different_exchanges_allowed(self, client, db):
|
||||
"""Test that same email can register for different exchanges."""
|
||||
future_close_date = datetime.utcnow() + timedelta(days=7)
|
||||
future_exchange_date = datetime.utcnow() + timedelta(days=14)
|
||||
|
||||
# Create two exchanges
|
||||
exchange1 = Exchange(
|
||||
slug="exchange1",
|
||||
name="Exchange 1",
|
||||
budget="$30",
|
||||
max_participants=10,
|
||||
registration_close_date=future_close_date,
|
||||
exchange_date=future_exchange_date,
|
||||
timezone="America/New_York",
|
||||
state=Exchange.STATE_REGISTRATION_OPEN,
|
||||
)
|
||||
exchange2 = Exchange(
|
||||
slug="exchange2",
|
||||
name="Exchange 2",
|
||||
budget="$40",
|
||||
max_participants=10,
|
||||
registration_close_date=future_close_date,
|
||||
exchange_date=future_exchange_date,
|
||||
timezone="America/New_York",
|
||||
state=Exchange.STATE_REGISTRATION_OPEN,
|
||||
)
|
||||
db.session.add(exchange1)
|
||||
db.session.add(exchange2)
|
||||
db.session.flush()
|
||||
|
||||
# Register for first exchange
|
||||
participant1 = Participant(
|
||||
exchange_id=exchange1.id,
|
||||
name="John Doe",
|
||||
email="john@example.com",
|
||||
reminder_enabled=True,
|
||||
)
|
||||
db.session.add(participant1)
|
||||
db.session.commit()
|
||||
|
||||
# Register same email for second exchange (should succeed)
|
||||
response = client.post(
|
||||
"/exchange/exchange2/register",
|
||||
data={
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
# Should redirect to success page
|
||||
assert response.status_code == 302
|
||||
assert "/exchange/exchange2/register/success" in response.location
|
||||
|
||||
# Should have two participants with same email but different exchanges
|
||||
participants = (
|
||||
db.session.query(Participant).filter_by(email="john@example.com").all()
|
||||
)
|
||||
assert len(participants) == 2
|
||||
assert participants[0].exchange_id != participants[1].exchange_id
|
||||
Reference in New Issue
Block a user