diff --git a/src/routes/participant.py b/src/routes/participant.py index 0acb2ea..c093f33 100644 --- a/src/routes/participant.py +++ b/src/routes/participant.py @@ -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, diff --git a/tests/integration/test_story_4_3_returning_participant_detection.py b/tests/integration/test_story_4_3_returning_participant_detection.py new file mode 100644 index 0000000..6a4e325 --- /dev/null +++ b/tests/integration/test_story_4_3_returning_participant_detection.py @@ -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