feat: implement Story 4.1 - Access Registration Page

Allows potential participants to view exchange details and access
the registration form via unique slug URLs.

Implementation:
- Added ParticipantRegistrationForm with name, email, gift_ideas, and reminder fields
- Created GET /exchange/<slug>/register route
- Built responsive registration template with exchange details
- Exempted participant routes from admin setup requirement
- Comprehensive test coverage for all scenarios

Acceptance Criteria Met:
- Valid slug displays registration form with exchange details
- Invalid slug returns 404
- Form includes all required fields with CSRF protection
- Registration deadline is displayed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 17:05:09 -07:00
parent abed4ac84a
commit 81e2cb8c86
6 changed files with 294 additions and 7 deletions

View File

@@ -0,0 +1,152 @@
"""Tests for Story 4.1: Access Registration Page.
As a potential participant, I want to access the registration page
via the unique link shared by the admin so that I can sign up for
the Secret Santa exchange.
"""
from datetime import datetime, timedelta
from src.models.exchange import Exchange
class TestStory41AccessRegistrationPage:
"""Test suite for Story 4.1: Access Registration Page."""
def test_get_registration_page_with_valid_slug(self, client, db):
"""Test registration page with valid slug displays exchange details."""
# Create an exchange
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",
description="Annual office gift exchange",
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.commit()
# Access registration page
response = client.get("/exchange/winter2025/register")
# Should return 200 OK
assert response.status_code == 200
# Should display exchange details
assert b"Office Secret Santa 2025" in response.data
assert b"Annual office gift exchange" in response.data
assert b"$50" in response.data
# Format the date to match what will be displayed
assert future_exchange_date.strftime("%Y-%m-%d").encode() in response.data
# Should display registration form
assert b'name="name"' in response.data
assert b'name="email"' in response.data
assert b'name="gift_ideas"' in response.data
assert b'name="reminder_enabled"' in response.data
def test_get_registration_page_with_invalid_slug(self, client):
"""Test accessing registration page with invalid slug returns 404."""
response = client.get("/exchange/invalid-slug/register")
assert response.status_code == 404
def test_registration_form_has_csrf_protection(self, client, db):
"""Test registration form includes POST method (CSRF via Flask-WTF)."""
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.commit()
response = client.get("/exchange/test123/register")
# Should have a POST form (CSRF protection is provided by Flask-WTF)
# Note: CSRF tokens are disabled in test mode, so we just verify the form exists
assert b'<form method="POST">' in response.data
assert b'<button type="submit">Register</button>' in response.data
def test_registration_page_displays_optional_description(self, client, db):
"""Test that optional description is displayed if present."""
future_close_date = datetime.utcnow() + timedelta(days=7)
future_exchange_date = datetime.utcnow() + timedelta(days=14)
exchange = Exchange(
slug="test123",
name="Test Exchange",
description="This is a test description",
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.commit()
response = client.get("/exchange/test123/register")
assert b"This is a test description" in response.data
def test_registration_page_without_description(self, client, db):
"""Test that page works when description is not provided."""
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.commit()
response = client.get("/exchange/test123/register")
# Should still work without description
assert response.status_code == 200
assert b"Test Exchange" in response.data
def test_registration_page_displays_registration_deadline(self, client, db):
"""Test that registration deadline is displayed."""
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.commit()
response = client.get("/exchange/test123/register")
assert future_close_date.strftime("%Y-%m-%d").encode() in response.data