feat: implement reminder preferences and withdrawal (Stories 6.3, 6.2)
Implement Phase 3 participant self-management features: Story 6.3 - Reminder Preferences: - Add ReminderPreferenceForm to participant forms - Add update_preferences route for preference updates - Update dashboard template with reminder preference toggle - Participants can enable/disable reminder emails at any time Story 6.2 - Withdrawal from Exchange: - Add can_withdraw utility function for state validation - Create WithdrawalService to handle withdrawal process - Add WithdrawForm with explicit confirmation requirement - Add withdraw route with GET (confirmation) and POST (process) - Add withdrawal confirmation email template - Update dashboard to show withdraw link when allowed - Withdrawal only allowed before registration closes - Session cleared after withdrawal, user redirected to registration All acceptance criteria met for both stories. Test coverage: 90.02% (156 tests passing) Linting and type checking: passed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ from datetime import UTC, datetime
|
||||
|
||||
from src.utils.participant import (
|
||||
can_update_profile,
|
||||
can_withdraw,
|
||||
get_active_participants,
|
||||
is_withdrawn,
|
||||
)
|
||||
@@ -121,3 +122,37 @@ def test_can_update_profile_completed_state(participant_factory, exchange_factor
|
||||
exchange = exchange_factory(state="completed")
|
||||
participant = participant_factory(exchange=exchange)
|
||||
assert can_update_profile(participant) is False
|
||||
|
||||
|
||||
def test_can_withdraw_draft_state(participant_factory, exchange_factory):
|
||||
"""Withdrawal allowed in draft state."""
|
||||
exchange = exchange_factory(state="draft")
|
||||
participant = participant_factory(exchange=exchange)
|
||||
assert can_withdraw(participant) is True
|
||||
|
||||
|
||||
def test_can_withdraw_registration_open(participant_factory, exchange_factory):
|
||||
"""Withdrawal allowed when registration open."""
|
||||
exchange = exchange_factory(state="registration_open")
|
||||
participant = participant_factory(exchange=exchange)
|
||||
assert can_withdraw(participant) is True
|
||||
|
||||
|
||||
def test_can_withdraw_registration_closed(participant_factory, exchange_factory):
|
||||
"""Withdrawal blocked when registration closed."""
|
||||
exchange = exchange_factory(state="registration_closed")
|
||||
participant = participant_factory(exchange=exchange)
|
||||
assert can_withdraw(participant) is False
|
||||
|
||||
|
||||
def test_can_withdraw_matched_state(participant_factory, exchange_factory):
|
||||
"""Withdrawal blocked after matching."""
|
||||
exchange = exchange_factory(state="matched")
|
||||
participant = participant_factory(exchange=exchange)
|
||||
assert can_withdraw(participant) is False
|
||||
|
||||
|
||||
def test_can_withdraw_already_withdrawn(participant_factory):
|
||||
"""Withdrawal blocked if already withdrawn."""
|
||||
participant = participant_factory(withdrawn_at=datetime.now(UTC))
|
||||
assert can_withdraw(participant) is False
|
||||
|
||||
Reference in New Issue
Block a user