chore: add production deployment config and upgrade path requirements
- Add docker-compose.yml and docker-compose.example.yml for production deployment - Add .env.example with all required environment variables - Update architect agent with upgrade path requirements - Update developer agent with migration best practices - Add Phase 3 design documents (v0.3.0) - Add ADR-0006 for participant state management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
252
docs/decisions/0006-participant-state-management.md
Normal file
252
docs/decisions/0006-participant-state-management.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# ADR-0006: Participant State Management
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2025-12-22
|
||||
**Deciders**: Architect
|
||||
**Phase**: v0.3.0
|
||||
|
||||
## Context
|
||||
|
||||
Participants need to manage their own profiles and potentially withdraw from exchanges. We need to decide:
|
||||
|
||||
1. When can participants update their profiles (name, gift ideas)?
|
||||
2. When can participants withdraw from an exchange?
|
||||
3. How should withdrawals be implemented (hard delete vs soft delete)?
|
||||
4. Should withdrawn participants be visible in any context?
|
||||
5. Can withdrawn participants re-join the same exchange?
|
||||
|
||||
These decisions impact data integrity, user experience, and admin workflows.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Profile Update Rules
|
||||
|
||||
**Profile updates are allowed until matching occurs.**
|
||||
|
||||
Participants can update their name and gift ideas when the exchange is in:
|
||||
- `draft` state
|
||||
- `registration_open` state
|
||||
- `registration_closed` state
|
||||
|
||||
Profile updates are **locked** when the exchange is in:
|
||||
- `matched` state
|
||||
- `completed` state
|
||||
|
||||
**Rationale**:
|
||||
- Gift ideas are sent to Secret Santas during match notification
|
||||
- Allowing changes after matching would create inconsistency (giver sees old version in email)
|
||||
- Name changes after matching could confuse participants about who they're buying for
|
||||
- Registration close is admin's signal to finalize participant list, not to lock profiles
|
||||
- Locking happens at matching, which is the point where profile data is "consumed" by the system
|
||||
|
||||
### 2. Withdrawal Rules
|
||||
|
||||
**Withdrawals are allowed until registration closes.**
|
||||
|
||||
Participants can withdraw when the exchange is in:
|
||||
- `draft` state
|
||||
- `registration_open` state
|
||||
|
||||
Withdrawals require admin intervention when the exchange is in:
|
||||
- `registration_closed` state (admin may be configuring exclusions)
|
||||
- `matched` state (would require re-matching)
|
||||
- `completed` state (historical record)
|
||||
|
||||
**Rationale**:
|
||||
- Before registration closes: minimal impact, just removes one participant
|
||||
- After registration closes: admin is likely configuring exclusions or preparing to match
|
||||
- After matching: re-matching is a significant operation that should be admin-controlled
|
||||
- Clear deadline (registration close) sets expectations for participants
|
||||
- Prevents last-minute dropouts that could disrupt matching
|
||||
|
||||
### 3. Withdrawal Implementation (Soft Delete)
|
||||
|
||||
**Withdrawals use soft delete via `withdrawn_at` timestamp.**
|
||||
|
||||
Technical implementation:
|
||||
- Set `participant.withdrawn_at = datetime.utcnow()` on withdrawal
|
||||
- Keep participant record in database
|
||||
- Filter out withdrawn participants in queries: `Participant.withdrawn_at.is_(None)`
|
||||
- Cascade rules remain unchanged (deleting exchange deletes all participants)
|
||||
|
||||
**Rationale**:
|
||||
- **Audit trail**: Preserves record of who registered and when they withdrew
|
||||
- **Email uniqueness**: Prevents re-registration with same email in same exchange (see Decision 5)
|
||||
- **Admin visibility**: Admins can see withdrawal history for troubleshooting
|
||||
- **Simplicity**: No cascade delete complexity or foreign key violations
|
||||
- **Existing pattern**: Data model already includes `withdrawn_at` field (v0.2.0 design)
|
||||
|
||||
Alternative considered: Hard delete participants on withdrawal
|
||||
- Rejected: Loses audit trail, allows immediate re-registration (see Decision 5)
|
||||
- Rejected: Requires careful cascade handling for tokens, exclusions
|
||||
- Rejected: Complicates participant count tracking
|
||||
|
||||
### 4. Withdrawn Participant Visibility
|
||||
|
||||
**Withdrawn participants are visible only to admin.**
|
||||
|
||||
Visibility rules:
|
||||
- **Participant list (participant view)**: Withdrawn participants excluded
|
||||
- **Participant list (admin view)**: Withdrawn participants shown with indicator (e.g., grayed out, "Withdrawn" badge)
|
||||
- **Participant count**: Counts exclude withdrawn participants
|
||||
- **Matching algorithm**: Withdrawn participants excluded from matching pool
|
||||
|
||||
**Rationale**:
|
||||
- **Privacy**: Respects participant's decision to withdraw (no public record)
|
||||
- **Admin needs**: Admin may need to see who withdrew (for follow-up, re-invites, etc.)
|
||||
- **Clean UX**: Participants see only active participants (less confusing)
|
||||
- **Data integrity**: Admin view preserves audit trail
|
||||
|
||||
### 5. Re-Registration After Withdrawal
|
||||
|
||||
**Withdrawn participants cannot re-join the same exchange (with same email).**
|
||||
|
||||
Technical enforcement:
|
||||
- Unique constraint on `(exchange_id, email)` remains in place
|
||||
- Soft delete doesn't remove the record, so email remains "taken"
|
||||
- Participant must use a different email to re-register
|
||||
|
||||
**Rationale**:
|
||||
- **Prevents gaming**: Stops participants from withdrawing to see participant list changes, then re-joining
|
||||
- **Simplifies logic**: No need to handle "re-activation" of withdrawn participants
|
||||
- **Clear consequence**: Withdrawal is final (as warned in UI)
|
||||
- **Data integrity**: Each participant registration is a distinct record
|
||||
|
||||
Alternative considered: Allow re-activation of withdrawn participants
|
||||
- Rejected: Complex state transitions (withdrawn → active → withdrawn → active)
|
||||
- Rejected: Unclear UX (does re-joining restore old profile or create new?)
|
||||
- Rejected: Enables abuse (withdraw/rejoin cycle)
|
||||
|
||||
If participant genuinely needs to rejoin:
|
||||
- Use a different email address (e.g., alias like user+exchange@example.com)
|
||||
- Or: Contact admin, who can manually delete the withdrawn record (future admin feature)
|
||||
|
||||
### 6. Reminder Preferences After Withdrawal
|
||||
|
||||
**Withdrawn participants do not receive reminder emails.**
|
||||
|
||||
Technical implementation:
|
||||
- Reminder job queries exclude withdrawn participants: `withdrawn_at IS NULL`
|
||||
- Reminder preference persists in database (for audit) but is not used
|
||||
|
||||
**Rationale**:
|
||||
- Withdrawn participants have no match to be reminded about
|
||||
- Sending reminders would be confusing and violate withdrawal expectations
|
||||
- Simple filter in reminder job handles this naturally
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Clear rules**: Participants know when they can update profiles or withdraw
|
||||
2. **Data integrity**: Matching always uses consistent profile data
|
||||
3. **Audit trail**: System preserves record of all registrations and withdrawals
|
||||
4. **Simple implementation**: Soft delete is easier than hard delete + cascades
|
||||
5. **Privacy**: Withdrawn participants not visible to other participants
|
||||
6. **Admin control**: Admin retains visibility for troubleshooting
|
||||
|
||||
### Negative
|
||||
|
||||
1. **No re-join**: Participants who withdraw accidentally must use different email
|
||||
2. **Email "wastage"**: Withdrawn participants' emails remain "taken" in that exchange
|
||||
3. **Database growth**: Withdrawn participants remain in database (minimal impact given small datasets)
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Clear warnings**: UI prominently warns that withdrawal is permanent and cannot be undone
|
||||
2. **Confirmation required**: Withdrawal requires explicit checkbox confirmation
|
||||
3. **Confirmation email**: Withdrawn participants receive email confirming withdrawal
|
||||
4. **Admin override** (future): Admin can manually delete withdrawn participants if needed
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### State Check Function
|
||||
|
||||
```python
|
||||
def can_update_profile(participant: Participant) -> bool:
|
||||
"""Check if participant can update their profile."""
|
||||
exchange = participant.exchange
|
||||
allowed_states = ['draft', 'registration_open', 'registration_closed']
|
||||
return exchange.state in allowed_states
|
||||
|
||||
|
||||
def can_withdraw(participant: Participant) -> bool:
|
||||
"""Check if participant can withdraw from the exchange."""
|
||||
if participant.withdrawn_at is not None:
|
||||
return False # Already withdrawn
|
||||
|
||||
exchange = participant.exchange
|
||||
allowed_states = ['draft', 'registration_open']
|
||||
return exchange.state in allowed_states
|
||||
```
|
||||
|
||||
### Query Pattern for Active Participants
|
||||
|
||||
```python
|
||||
# Get active participants only
|
||||
active_participants = Participant.query.filter(
|
||||
Participant.exchange_id == exchange_id,
|
||||
Participant.withdrawn_at.is_(None)
|
||||
).all()
|
||||
|
||||
# Count active participants
|
||||
active_count = Participant.query.filter(
|
||||
Participant.exchange_id == exchange_id,
|
||||
Participant.withdrawn_at.is_(None)
|
||||
).count()
|
||||
```
|
||||
|
||||
### Admin View Enhancement (Future)
|
||||
|
||||
```python
|
||||
# Admin can see all participants including withdrawn
|
||||
all_participants = Participant.query.filter(
|
||||
Participant.exchange_id == exchange_id
|
||||
).all()
|
||||
|
||||
# Template can check: participant.withdrawn_at is not None
|
||||
```
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0002: Authentication Strategy](0002-authentication-strategy.md) - Participant session management
|
||||
- [ADR-0003: Participant Session Scoping](0003-participant-session-scoping.md) - Session behavior on withdrawal
|
||||
- [v0.2.0 Data Model](../designs/v0.2.0/data-model.md) - `withdrawn_at` field design
|
||||
- [v0.3.0 Participant Self-Management](../designs/v0.3.0/participant-self-management.md) - Implementation details
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Phase 6: Admin Participant Management
|
||||
|
||||
When implementing admin participant removal (Epic 9):
|
||||
- Admin should be able to hard delete withdrawn participants (cleanup)
|
||||
- Admin should be able to remove active participants (sets withdrawn_at + sends notification)
|
||||
- Admin should see withdrawal history in participant list
|
||||
|
||||
### Phase 8: Matching
|
||||
|
||||
Matching algorithm must:
|
||||
- Filter participants by `withdrawn_at IS NULL`
|
||||
- Validate participant count >= 3 (after filtering)
|
||||
- Handle case where withdrawals reduce count below minimum
|
||||
|
||||
### Potential Future Enhancement: Re-Activation
|
||||
|
||||
If user demand requires allowing re-join:
|
||||
- Add `reactivated_at` timestamp
|
||||
- Track withdrawal/reactivation history (audit log)
|
||||
- Clear `withdrawn_at` on re-activation
|
||||
- Send re-activation email
|
||||
- Complexity: High, defer until proven necessary
|
||||
|
||||
## References
|
||||
|
||||
- [Product Backlog](../BACKLOG.md) - Epic 6: Participant Self-Management
|
||||
- [Project Overview](../PROJECT_OVERVIEW.md) - Self-management principles
|
||||
- [v0.2.0 Data Model](../designs/v0.2.0/data-model.md) - Participant table schema
|
||||
|
||||
---
|
||||
|
||||
**Decision Date**: 2025-12-22
|
||||
**Architect**: Claude Opus 4.5
|
||||
**Status**: Accepted for v0.3.0
|
||||
278
docs/designs/v0.3.0/README.md
Normal file
278
docs/designs/v0.3.0/README.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Phase 3 (v0.3.0) Design Documentation
|
||||
|
||||
**Version**: 0.3.0
|
||||
**Date**: 2025-12-22
|
||||
**Status**: Ready for Implementation
|
||||
|
||||
## Quick Start
|
||||
|
||||
Phase 3 implements participant self-management features, building on the authentication foundation from Phase 2.
|
||||
|
||||
**Core Features**:
|
||||
- Participant list view (see who else registered)
|
||||
- Profile updates (name and gift ideas)
|
||||
- Reminder email preferences
|
||||
- Participant withdrawal (before registration closes)
|
||||
|
||||
## Document Index
|
||||
|
||||
### 1. [System Overview](overview.md)
|
||||
High-level architecture, goals, and design decisions for Phase 3.
|
||||
|
||||
**Read this first** to understand:
|
||||
- What's in scope for v0.3.0
|
||||
- Key design decisions (profile locks, withdrawal rules)
|
||||
- Data flow diagrams
|
||||
- State machine changes
|
||||
|
||||
**Key sections**:
|
||||
- Phase Goals (page 1)
|
||||
- Key Design Decisions (page 2)
|
||||
- State Machine Impact (page 4)
|
||||
|
||||
### 2. [Participant Self-Management Component Design](participant-self-management.md)
|
||||
Detailed component specifications for all Phase 3 features.
|
||||
|
||||
**Use this for**:
|
||||
- Exact function signatures and implementations
|
||||
- Form definitions
|
||||
- Route specifications
|
||||
- Template structures
|
||||
- Email templates
|
||||
|
||||
**Key sections**:
|
||||
- Business Logic Functions (page 1)
|
||||
- Forms (page 6)
|
||||
- Routes (page 8)
|
||||
- Templates (page 11)
|
||||
- Security Checklist (page 19)
|
||||
|
||||
### 3. [Test Plan](test-plan.md)
|
||||
Comprehensive testing specifications for Phase 3.
|
||||
|
||||
**Use this for**:
|
||||
- Unit test cases and fixtures
|
||||
- Integration test scenarios
|
||||
- Acceptance test procedures
|
||||
- Manual QA steps
|
||||
- Coverage requirements
|
||||
|
||||
**Key sections**:
|
||||
- Unit Tests (page 1)
|
||||
- Integration Tests (page 4)
|
||||
- Acceptance Tests (page 9)
|
||||
- Edge Cases (page 11)
|
||||
|
||||
### 4. [Implementation Guide](implementation-guide.md)
|
||||
Step-by-step implementation instructions using TDD.
|
||||
|
||||
**Follow this to**:
|
||||
- Implement features in correct order
|
||||
- Write tests first, then code
|
||||
- Verify each feature before moving on
|
||||
- Create proper commits and PRs
|
||||
|
||||
**Key sections**:
|
||||
- Implementation Order (page 1)
|
||||
- Phase 3.1: Participant List (page 2)
|
||||
- Phase 3.2: Profile Updates (page 4)
|
||||
- Phase 3.3: Reminder Preferences (page 7)
|
||||
- Phase 3.4: Withdrawal (page 9)
|
||||
- Final Steps (page 14)
|
||||
|
||||
## Architecture Decision Records
|
||||
|
||||
### [ADR-0006: Participant State Management](../../decisions/0006-participant-state-management.md)
|
||||
Documents key decisions about when participants can update profiles, withdraw, and how withdrawals are implemented.
|
||||
|
||||
**Key decisions**:
|
||||
- Profile updates allowed until matching
|
||||
- Withdrawals allowed until registration closes
|
||||
- Soft delete implementation (withdrawn_at timestamp)
|
||||
- Withdrawn participants visible only to admin
|
||||
- No re-registration with same email
|
||||
|
||||
## User Stories Implemented
|
||||
|
||||
Phase 3 completes these backlog items:
|
||||
|
||||
### Epic 4: Participant Registration
|
||||
- ✅ **Story 4.5**: View Participant List (Pre-Matching)
|
||||
|
||||
### Epic 6: Participant Self-Management
|
||||
- ✅ **Story 6.1**: Update Profile
|
||||
- ✅ **Story 6.2**: Withdraw from Exchange
|
||||
- ✅ **Story 6.3**: Update Reminder Preferences
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Prerequisites (from Phase 2)
|
||||
- ✅ Participant model with `withdrawn_at` field
|
||||
- ✅ Participant authentication (@participant_required decorator)
|
||||
- ✅ Participant dashboard route
|
||||
- ✅ Email service for sending emails
|
||||
|
||||
### No New Dependencies
|
||||
Phase 3 requires **no new**:
|
||||
- Python packages
|
||||
- Database migrations
|
||||
- Environment variables
|
||||
- External services
|
||||
|
||||
## Technical Highlights
|
||||
|
||||
### New Files Created
|
||||
```
|
||||
src/
|
||||
utils/participant.py # Business logic functions
|
||||
services/withdrawal.py # Withdrawal service
|
||||
|
||||
templates/
|
||||
participant/
|
||||
profile_edit.html # Profile edit page
|
||||
withdraw.html # Withdrawal confirmation
|
||||
emails/
|
||||
participant/
|
||||
withdrawal_confirmation.html # Withdrawal email
|
||||
withdrawal_confirmation.txt # Plain text version
|
||||
|
||||
tests/
|
||||
unit/
|
||||
test_participant_utils.py # Unit tests for business logic
|
||||
test_withdrawal_service.py # Unit tests for withdrawal
|
||||
integration/
|
||||
test_profile_update.py # Profile update integration tests
|
||||
test_withdrawal.py # Withdrawal integration tests
|
||||
test_participant_list.py # Participant list tests
|
||||
test_reminder_preferences.py # Preference update tests
|
||||
```
|
||||
|
||||
### Modified Files
|
||||
```
|
||||
src/
|
||||
routes/participant.py # New routes added
|
||||
forms/participant.py # New forms added
|
||||
services/email.py # Withdrawal email method added
|
||||
|
||||
templates/
|
||||
participant/dashboard.html # Enhanced with participant list
|
||||
```
|
||||
|
||||
## Design Principles Applied
|
||||
|
||||
Phase 3 adheres to project principles:
|
||||
|
||||
1. **Simplicity First**
|
||||
- No new database tables (uses existing fields)
|
||||
- No new external dependencies
|
||||
- Soft delete instead of complex cascade handling
|
||||
|
||||
2. **State-Based Permissions**
|
||||
- Clear rules about when operations are allowed
|
||||
- Based on exchange state (draft, open, closed, matched)
|
||||
- Easy to test and reason about
|
||||
|
||||
3. **TDD Approach**
|
||||
- Implementation guide follows test-first methodology
|
||||
- Every feature has unit and integration tests
|
||||
- 80%+ coverage maintained
|
||||
|
||||
4. **Security by Design**
|
||||
- All routes require authentication
|
||||
- State validation prevents unauthorized operations
|
||||
- CSRF protection on all POST operations
|
||||
- Input sanitization via WTForms and Jinja2
|
||||
|
||||
5. **Privacy-Conscious**
|
||||
- Participant list shows names only (no emails)
|
||||
- Withdrawn participants hidden from other participants
|
||||
- Gift ideas not revealed until matching
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
Use this to track progress:
|
||||
|
||||
- [ ] **Phase 3.1: Participant List View**
|
||||
- [ ] Create `src/utils/participant.py`
|
||||
- [ ] Write unit tests for utility functions
|
||||
- [ ] Update dashboard route
|
||||
- [ ] Update dashboard template
|
||||
- [ ] Write integration tests
|
||||
- [ ] Manual QA
|
||||
|
||||
- [ ] **Phase 3.2: Profile Updates**
|
||||
- [ ] Add `can_update_profile()` function
|
||||
- [ ] Write unit tests for state validation
|
||||
- [ ] Create `ProfileUpdateForm`
|
||||
- [ ] Create profile edit route
|
||||
- [ ] Create profile edit template
|
||||
- [ ] Update dashboard with edit link
|
||||
- [ ] Write integration tests
|
||||
- [ ] Manual QA
|
||||
|
||||
- [ ] **Phase 3.3: Reminder Preferences**
|
||||
- [ ] Create `ReminderPreferenceForm`
|
||||
- [ ] Create preference update route
|
||||
- [ ] Update dashboard with preference form
|
||||
- [ ] Write integration tests
|
||||
- [ ] Manual QA
|
||||
|
||||
- [ ] **Phase 3.4: Withdrawal**
|
||||
- [ ] Add `can_withdraw()` function
|
||||
- [ ] Create `src/services/withdrawal.py`
|
||||
- [ ] Write unit tests for withdrawal service
|
||||
- [ ] Create `WithdrawForm`
|
||||
- [ ] Create withdrawal route
|
||||
- [ ] Create email templates
|
||||
- [ ] Update email service
|
||||
- [ ] Create withdrawal template
|
||||
- [ ] Update dashboard with withdraw link
|
||||
- [ ] Write integration tests
|
||||
- [ ] Manual QA
|
||||
|
||||
- [ ] **Final Steps**
|
||||
- [ ] Run all tests (≥ 80% coverage)
|
||||
- [ ] Run linting and type checking
|
||||
- [ ] Complete manual QA from test plan
|
||||
- [ ] Update documentation if needed
|
||||
- [ ] Create feature branch
|
||||
- [ ] Commit changes
|
||||
- [ ] Create pull request
|
||||
|
||||
## Success Criteria
|
||||
|
||||
Phase 3 is complete when:
|
||||
|
||||
1. ✅ All user stories have passing acceptance tests
|
||||
2. ✅ Code coverage ≥ 80%
|
||||
3. ✅ All linting and type checking passes
|
||||
4. ✅ Manual QA completed
|
||||
5. ✅ Security checklist verified
|
||||
6. ✅ Accessibility tests pass
|
||||
7. ✅ Browser compatibility verified
|
||||
8. ✅ Phase 2 regression tests still pass
|
||||
9. ✅ Documentation updated
|
||||
10. ✅ Pull request approved and merged
|
||||
|
||||
## What's Next: Phase 4
|
||||
|
||||
After Phase 3 is complete, the next logical phase is:
|
||||
|
||||
**Phase 4 (v0.4.0)**: Post-Matching Participant Experience (Epic 11)
|
||||
- View assigned recipient (match assignment)
|
||||
- View recipient's gift ideas
|
||||
- View exchange information post-matching
|
||||
- Participant list (post-matching version)
|
||||
|
||||
This builds on Phase 3's participant dashboard foundation and enables the core Secret Santa experience after admin has matched participants.
|
||||
|
||||
## Questions?
|
||||
|
||||
- Review the [Project Overview](../../PROJECT_OVERVIEW.md) for product vision
|
||||
- Check the [Backlog](../../BACKLOG.md) for user stories
|
||||
- See [v0.2.0 Design](../v0.2.0/overview.md) for foundation architecture
|
||||
- Consult existing [ADRs](../../decisions/) for architectural context
|
||||
|
||||
---
|
||||
|
||||
**Phase 3 Design Status**: ✅ Complete and Ready for Implementation
|
||||
1269
docs/designs/v0.3.0/implementation-guide.md
Normal file
1269
docs/designs/v0.3.0/implementation-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
566
docs/designs/v0.3.0/overview.md
Normal file
566
docs/designs/v0.3.0/overview.md
Normal file
@@ -0,0 +1,566 @@
|
||||
# System Overview - v0.3.0
|
||||
|
||||
**Version**: 0.3.0
|
||||
**Date**: 2025-12-22
|
||||
**Status**: Phase 3 Design
|
||||
|
||||
## Introduction
|
||||
|
||||
This document describes the design for Phase 3 of Sneaky Klaus, building on the participant authentication foundation established in v0.2.0. This phase focuses on participant self-management capabilities and pre-matching participant experience.
|
||||
|
||||
**Phase 3 Scope**: Implement Epic 6 (Participant Self-Management) and Epic 4.5 (View Participant List Pre-Matching), enabling participants to manage their own profiles and see who else has registered before matching occurs.
|
||||
|
||||
## Phase Goals
|
||||
|
||||
The primary goals for v0.3.0 are:
|
||||
|
||||
1. **Enable participant self-service**: Allow participants to update their profiles without admin intervention
|
||||
2. **Support graceful withdrawals**: Handle participants who need to drop out before matching
|
||||
3. **Build social engagement**: Let participants see who else is participating (pre-matching only)
|
||||
4. **Maintain data integrity**: Ensure profile changes and withdrawals don't break the exchange flow
|
||||
|
||||
## User Stories in Scope
|
||||
|
||||
### Epic 6: Participant Self-Management
|
||||
|
||||
- **6.1 Update Profile**: Participants can edit their name and gift ideas before matching
|
||||
- **6.2 Withdraw from Exchange**: Participants can opt out before registration closes
|
||||
- **6.3 Update Reminder Preferences**: Participants can toggle reminder emails on/off
|
||||
|
||||
### Epic 4: Participant Registration (Continuation)
|
||||
|
||||
- **4.5 View Participant List (Pre-Matching)**: Registered participants can see who else has joined
|
||||
|
||||
## Out of Scope for v0.3.0
|
||||
|
||||
The following features are explicitly **not** included in this phase:
|
||||
|
||||
- Post-matching participant experience (Epic 11) - deferred to Phase 4
|
||||
- Matching system (Epic 8) - deferred to Phase 5
|
||||
- Admin exchange management beyond what v0.1.0 provided
|
||||
- Participant removal by admin (Epic 9) - deferred to Phase 6
|
||||
- Notification emails beyond registration confirmation (Epic 10) - partial in Phase 2, remainder in Phase 7
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Phase 3 builds incrementally on the v0.2.0 architecture with no new infrastructure components:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Phase 3 Additions"
|
||||
ProfileUpdate[Profile Update UI]
|
||||
WithdrawUI[Withdraw UI]
|
||||
ParticipantList[Participant List View]
|
||||
|
||||
ProfileUpdate --> ParticipantRoutes
|
||||
WithdrawUI --> ParticipantRoutes
|
||||
ParticipantList --> ParticipantRoutes
|
||||
end
|
||||
|
||||
subgraph "Existing v0.2.0 Foundation"
|
||||
ParticipantRoutes[Participant Routes]
|
||||
ParticipantAuth[Participant Auth Decorator]
|
||||
ParticipantModel[Participant Model]
|
||||
ExchangeModel[Exchange Model]
|
||||
Database[(SQLite)]
|
||||
|
||||
ParticipantRoutes --> ParticipantAuth
|
||||
ParticipantRoutes --> ParticipantModel
|
||||
ParticipantRoutes --> ExchangeModel
|
||||
ParticipantModel --> Database
|
||||
ExchangeModel --> Database
|
||||
end
|
||||
```
|
||||
|
||||
## Technical Design Principles
|
||||
|
||||
This phase adheres to the project's core principles established in v0.1.0 and v0.2.0:
|
||||
|
||||
1. **No new database tables**: All features use existing Participant and Exchange models
|
||||
2. **No new external dependencies**: Pure Flask/SQLAlchemy implementation
|
||||
3. **Soft deletes**: Withdrawals use `withdrawn_at` timestamp rather than hard deletes
|
||||
4. **State-based permissions**: Operations restricted based on exchange state
|
||||
5. **TDD approach**: Write tests first, implement to pass
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### 1. Profile Update Restrictions
|
||||
|
||||
**Decision**: Profile updates are only allowed when exchange is in `draft`, `registration_open`, or `registration_closed` states. After matching, profiles are locked.
|
||||
|
||||
**Rationale**:
|
||||
- Gift ideas are sent to givers during match notification - changes after matching would create inconsistency
|
||||
- Name changes after matching could confuse participants
|
||||
- Clear state transition prevents data inconsistency
|
||||
|
||||
**Implementation**:
|
||||
```python
|
||||
def can_update_profile(participant: Participant) -> bool:
|
||||
"""Check if participant can update their profile."""
|
||||
exchange = participant.exchange
|
||||
return exchange.state in ['draft', 'registration_open', 'registration_closed']
|
||||
```
|
||||
|
||||
### 2. Withdrawal Rules
|
||||
|
||||
**Decision**: Withdrawals are only allowed before registration closes. After registration closes, admin intervention required.
|
||||
|
||||
**Rationale**:
|
||||
- Pre-closure: minimal impact, just removes one participant
|
||||
- Post-closure: admin likely already configuring exclusions or has matched
|
||||
- Post-matching: requires re-match, admin should make this decision
|
||||
- Clear deadline prevents last-minute dropouts
|
||||
|
||||
**Implementation**:
|
||||
```python
|
||||
def can_withdraw(participant: Participant) -> bool:
|
||||
"""Check if participant can withdraw themselves."""
|
||||
exchange = participant.exchange
|
||||
return exchange.state in ['draft', 'registration_open']
|
||||
```
|
||||
|
||||
### 3. Withdrawal Implementation (Soft Delete)
|
||||
|
||||
**Decision**: Use existing `withdrawn_at` timestamp field, don't delete participant records.
|
||||
|
||||
**Rationale**:
|
||||
- Data model already supports soft deletes (v0.2.0 design)
|
||||
- Preserves audit trail of who registered
|
||||
- Prevents re-use of email during same exchange
|
||||
- Allows admin to see withdrawal history
|
||||
- Simplifies database constraints (no cascade delete issues)
|
||||
|
||||
**Implementation**:
|
||||
```python
|
||||
def withdraw_participant(participant: Participant):
|
||||
"""Mark participant as withdrawn."""
|
||||
participant.withdrawn_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
```
|
||||
|
||||
### 4. Participant List Visibility
|
||||
|
||||
**Decision**: Show participant list to all registered participants, but only show names (not emails or gift ideas).
|
||||
|
||||
**Rationale**:
|
||||
- Social aspect: participants want to know who's participating
|
||||
- Privacy: emails are admin-only
|
||||
- Security: gift ideas are for givers only (post-matching)
|
||||
- Pre-matching only: post-matching handled in Phase 4
|
||||
|
||||
**Display Rules**:
|
||||
- Show: participant names, count
|
||||
- Hide: emails, gift ideas, withdrawn participants
|
||||
- Filter: exclude withdrawn participants from list
|
||||
|
||||
### 5. Reminder Preference Changes
|
||||
|
||||
**Decision**: Allow reminder preference changes at any time before exchange completes.
|
||||
|
||||
**Rationale**:
|
||||
- Low-impact change (doesn't affect matching or other participants)
|
||||
- User preference should be flexible
|
||||
- No technical reason to restrict after matching
|
||||
- Allows opting out if circumstances change
|
||||
|
||||
**Implementation**: Simple boolean toggle, no state restrictions.
|
||||
|
||||
### 6. Form Validation Strategy
|
||||
|
||||
**Decision**: Use server-side validation with WTForms, add client-side hints for UX.
|
||||
|
||||
**Rationale**:
|
||||
- Consistent with Phase 2 implementation decisions
|
||||
- Security: never trust client-side validation
|
||||
- UX: client-side provides immediate feedback
|
||||
- Progressive enhancement: works without JavaScript
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Profile Update Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P as Participant Browser
|
||||
participant F as Flask App
|
||||
participant DB as SQLite Database
|
||||
|
||||
P->>F: GET /participant/profile/edit
|
||||
F->>F: Check @participant_required
|
||||
F->>DB: Load participant & exchange
|
||||
F->>F: Check can_update_profile()
|
||||
F-->>P: Render edit form (or error)
|
||||
|
||||
P->>F: POST /participant/profile/edit (name, gift_ideas)
|
||||
F->>F: Validate form
|
||||
F->>F: Check can_update_profile()
|
||||
F->>DB: Update participant record
|
||||
DB-->>F: Success
|
||||
F-->>P: Redirect to dashboard with success message
|
||||
```
|
||||
|
||||
### Withdrawal Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P as Participant Browser
|
||||
participant F as Flask App
|
||||
participant DB as SQLite Database
|
||||
participant E as Email Service
|
||||
|
||||
P->>F: POST /participant/withdraw (with confirmation)
|
||||
F->>F: Check @participant_required
|
||||
F->>DB: Load participant & exchange
|
||||
F->>F: Check can_withdraw()
|
||||
F->>DB: Set withdrawn_at timestamp
|
||||
DB-->>F: Success
|
||||
F->>E: Send withdrawal confirmation email
|
||||
E-->>F: Email sent
|
||||
F->>F: Clear session (log out participant)
|
||||
F-->>P: Redirect to public page with confirmation
|
||||
```
|
||||
|
||||
### Participant List Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P as Participant Browser
|
||||
participant F as Flask App
|
||||
participant DB as SQLite Database
|
||||
|
||||
P->>F: GET /participant/dashboard
|
||||
F->>F: Check @participant_required
|
||||
F->>DB: Load participant's exchange
|
||||
F->>DB: Query active participants (withdrawn_at IS NULL)
|
||||
DB-->>F: Participant list (names only)
|
||||
F-->>P: Render dashboard with participant list
|
||||
```
|
||||
|
||||
## State Machine Impact
|
||||
|
||||
Phase 3 doesn't add new exchange states, but adds participant-level state:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Registered: Registration
|
||||
Registered --> UpdatedProfile: Edit profile
|
||||
UpdatedProfile --> UpdatedProfile: Edit again
|
||||
UpdatedProfile --> Withdrawn: Withdraw
|
||||
Registered --> Withdrawn: Withdraw
|
||||
UpdatedProfile --> Matched: Admin matches
|
||||
Registered --> Matched: Admin matches
|
||||
Withdrawn --> [*]
|
||||
|
||||
note right of Withdrawn
|
||||
Soft delete: withdrawn_at set
|
||||
Cannot re-activate
|
||||
Must re-register with new email
|
||||
end note
|
||||
|
||||
note right of UpdatedProfile
|
||||
Only before matching
|
||||
Name and gift_ideas editable
|
||||
end note
|
||||
```
|
||||
|
||||
## Component Design
|
||||
|
||||
### Routes (participant_bp)
|
||||
|
||||
New routes added to existing `src/routes/participant.py`:
|
||||
|
||||
| Route | Method | Auth | Description |
|
||||
|-------|--------|------|-------------|
|
||||
| `/participant/profile/edit` | GET, POST | @participant_required | Edit profile (name, gift ideas) |
|
||||
| `/participant/preferences` | POST | @participant_required | Update reminder preference |
|
||||
| `/participant/withdraw` | POST | @participant_required | Withdraw from exchange |
|
||||
|
||||
Existing routes used:
|
||||
- `/participant/dashboard` - enhanced to show participant list
|
||||
|
||||
### Forms (src/forms/participant.py)
|
||||
|
||||
New forms:
|
||||
|
||||
**ProfileUpdateForm**:
|
||||
```python
|
||||
class ProfileUpdateForm(FlaskForm):
|
||||
name = StringField('Name', validators=[DataRequired(), Length(max=255)])
|
||||
gift_ideas = TextAreaField('Gift Ideas', validators=[Length(max=10000)])
|
||||
submit = SubmitField('Save Changes')
|
||||
```
|
||||
|
||||
**ReminderPreferenceForm**:
|
||||
```python
|
||||
class ReminderPreferenceForm(FlaskForm):
|
||||
reminder_enabled = BooleanField('Send me reminders')
|
||||
submit = SubmitField('Update Preferences')
|
||||
```
|
||||
|
||||
**WithdrawForm**:
|
||||
```python
|
||||
class WithdrawForm(FlaskForm):
|
||||
confirm = BooleanField('I understand this cannot be undone',
|
||||
validators=[DataRequired()])
|
||||
submit = SubmitField('Withdraw from Exchange')
|
||||
```
|
||||
|
||||
### Templates
|
||||
|
||||
New templates in `templates/participant/`:
|
||||
|
||||
- `profile_edit.html` - Profile update form
|
||||
- `withdraw.html` - Withdrawal confirmation page (with warnings)
|
||||
- `participant_list.html` - Reusable component for displaying participant names
|
||||
|
||||
Enhanced templates:
|
||||
- `dashboard.html` - Add participant list section
|
||||
|
||||
### Email Templates
|
||||
|
||||
New email template in `templates/emails/participant/`:
|
||||
|
||||
- `withdrawal_confirmation.html` - Sent when participant withdraws
|
||||
- `withdrawal_confirmation.txt` - Plain text version
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### 1. Authorization Checks
|
||||
|
||||
Every participant operation must verify:
|
||||
- User is authenticated as participant (@participant_required)
|
||||
- User owns the resource (participant_id matches session)
|
||||
- Exchange state allows the operation (can_update_profile, can_withdraw)
|
||||
|
||||
### 2. CSRF Protection
|
||||
|
||||
All POST operations require CSRF tokens (WTForms automatic).
|
||||
|
||||
### 3. Input Validation
|
||||
|
||||
- Name: 1-255 characters, required
|
||||
- Gift ideas: 0-10,000 characters, optional
|
||||
- All inputs sanitized via Jinja2 auto-escaping
|
||||
|
||||
### 4. Privacy
|
||||
|
||||
Participant list shows:
|
||||
- Names only (public within exchange)
|
||||
- Not emails (admin-only)
|
||||
- Not gift ideas (giver-only post-matching)
|
||||
- Not withdrawn participants (respect withdrawal privacy)
|
||||
|
||||
### 5. Rate Limiting
|
||||
|
||||
No new rate limiting needed:
|
||||
- Profile updates: legitimate user operation, no abuse vector
|
||||
- Withdrawals: self-limiting (can only withdraw once)
|
||||
- Participant list: read-only, authenticated
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Test business logic in isolation:
|
||||
- `can_update_profile()` logic for each exchange state
|
||||
- `can_withdraw()` logic for each exchange state
|
||||
- Soft delete implementation (withdrawn_at timestamp)
|
||||
- Participant list filtering (exclude withdrawn)
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Test full request/response cycles:
|
||||
- Profile update happy path
|
||||
- Profile update when locked (post-matching)
|
||||
- Withdrawal happy path
|
||||
- Withdrawal when not allowed
|
||||
- Participant list rendering
|
||||
|
||||
### Test Data Setup
|
||||
|
||||
Reuse existing test fixtures from Phase 2, add:
|
||||
- Multiple participants in same exchange
|
||||
- Participants in different states (registered, withdrawn)
|
||||
- Exchanges in different states
|
||||
|
||||
### Coverage Target
|
||||
|
||||
Maintain 80%+ coverage established in previous phases.
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Expected Errors
|
||||
|
||||
| Scenario | HTTP Status | User Message | Action |
|
||||
|----------|-------------|--------------|--------|
|
||||
| Update profile after matching | 400 | "Your profile is locked after matching" | Redirect to dashboard |
|
||||
| Withdraw after registration closes | 400 | "Withdrawal deadline has passed. Contact admin." | Redirect to dashboard |
|
||||
| Invalid form data | 400 | Field-specific errors | Re-render form with errors |
|
||||
| Already withdrawn | 400 | "You have already withdrawn" | Redirect to public page |
|
||||
|
||||
### Unexpected Errors
|
||||
|
||||
- Database errors: Log, flash generic error, redirect safely
|
||||
- Missing participant record: Clear session, redirect to landing page
|
||||
- Orphaned session: Clear session, redirect to landing page
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Database Queries
|
||||
|
||||
**Participant List Query**:
|
||||
```python
|
||||
# Efficient query with single DB hit
|
||||
participants = Participant.query.filter(
|
||||
Participant.exchange_id == exchange_id,
|
||||
Participant.withdrawn_at.is_(None)
|
||||
).order_by(Participant.name).all()
|
||||
```
|
||||
|
||||
**Profile Load**:
|
||||
- Already loaded by @participant_required decorator (uses `g.participant`)
|
||||
- No additional query needed
|
||||
|
||||
### Caching
|
||||
|
||||
Not needed for v0.3.0:
|
||||
- Participant counts are small (3-100 typical)
|
||||
- Updates are infrequent
|
||||
- Reads are authenticated (no public caching)
|
||||
|
||||
## Migration Requirements
|
||||
|
||||
**No database migrations required for v0.3.0.**
|
||||
|
||||
All fields already exist:
|
||||
- `participant.name` - editable
|
||||
- `participant.gift_ideas` - editable
|
||||
- `participant.reminder_enabled` - editable
|
||||
- `participant.withdrawn_at` - set on withdrawal
|
||||
|
||||
## Deployment Impact
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
None. v0.3.0 is fully backward compatible with v0.2.0.
|
||||
|
||||
### Configuration Changes
|
||||
|
||||
None. No new environment variables or configuration needed.
|
||||
|
||||
### Data Migration
|
||||
|
||||
None. Existing data fully compatible.
|
||||
|
||||
## User Experience Flow
|
||||
|
||||
### Participant Journey (Pre-Matching)
|
||||
|
||||
1. **Register** (Phase 2)
|
||||
- Receive confirmation email with magic link
|
||||
|
||||
2. **View Dashboard** (Phase 2 + Phase 3)
|
||||
- See own information
|
||||
- **NEW**: See list of other participants (names only)
|
||||
|
||||
3. **Update Profile** (Phase 3)
|
||||
- Click "Edit Profile" from dashboard
|
||||
- Update name or gift ideas
|
||||
- Save changes
|
||||
- See confirmation message
|
||||
|
||||
4. **Change Reminder Preference** (Phase 3)
|
||||
- Toggle reminder checkbox on dashboard
|
||||
- See confirmation message
|
||||
|
||||
5. **Withdraw (if needed)** (Phase 3)
|
||||
- Click "Withdraw" from dashboard
|
||||
- See warning about permanence
|
||||
- Confirm withdrawal
|
||||
- Receive confirmation email
|
||||
- Logged out and redirected to public page
|
||||
|
||||
## Admin Experience Impact
|
||||
|
||||
Phase 3 adds visibility for admins:
|
||||
|
||||
### Exchange Detail View (Enhancement)
|
||||
|
||||
Add to existing exchange detail page:
|
||||
- Show participant list with withdrawn indicator
|
||||
- Display: "5 active, 1 withdrawn"
|
||||
- Allow admin to see withdrawn participants (grayed out)
|
||||
|
||||
**Implementation Note**: This is a minor enhancement to existing admin routes, not a major feature. Can be implemented alongside participant features or deferred to Phase 6 (Admin Participant Management).
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Phase 4 Dependencies
|
||||
|
||||
Phase 3 sets up foundations for Phase 4 (Post-Matching Experience):
|
||||
- Participant list already implemented (will be reused post-matching)
|
||||
- Profile lock logic (can_update_profile) prevents changes after matching
|
||||
- Dashboard structure ready to show match assignment
|
||||
|
||||
### Potential Enhancements (Out of Scope)
|
||||
|
||||
Not included in v0.3.0 but possible future additions:
|
||||
- Email notification to admin when participant withdraws (Epic 10.5)
|
||||
- Allow participants to indicate dietary restrictions or allergies
|
||||
- Allow participants to add profile pictures
|
||||
- Allow participants to message admin (anonymous contact form)
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
Phase 3 is complete when:
|
||||
|
||||
1. ✅ Participants can update their name and gift ideas before matching
|
||||
2. ✅ Participants cannot update profile after matching occurs
|
||||
3. ✅ Participants can withdraw before registration closes
|
||||
4. ✅ Participants cannot withdraw after registration closes
|
||||
5. ✅ Withdrawn participants receive confirmation email
|
||||
6. ✅ Withdrawn participants are logged out and removed from participant list
|
||||
7. ✅ Participants can view list of other registered participants (names only)
|
||||
8. ✅ Participant list excludes withdrawn participants
|
||||
9. ✅ Participants can toggle reminder preferences at any time
|
||||
10. ✅ All operations require participant authentication
|
||||
11. ✅ All operations validate exchange state appropriately
|
||||
12. ✅ All user stories have passing integration tests
|
||||
13. ✅ Code coverage remains at 80%+
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
Recommended implementation order (TDD, vertical slices):
|
||||
|
||||
### Phase 3.1: Participant List View
|
||||
- Story 4.5: View Participant List (Pre-Matching)
|
||||
- Simplest feature, no state changes
|
||||
- Sets up dashboard enhancements
|
||||
|
||||
### Phase 3.2: Profile Updates
|
||||
- Story 6.1: Update Profile
|
||||
- Core self-management feature
|
||||
- Tests state-based permissions
|
||||
|
||||
### Phase 3.3: Reminder Preferences
|
||||
- Story 6.3: Update Reminder Preferences
|
||||
- Simple toggle, low risk
|
||||
- Quick win
|
||||
|
||||
### Phase 3.4: Withdrawal
|
||||
- Story 6.2: Withdraw from Exchange
|
||||
- Most complex (state changes, email, logout)
|
||||
- Benefits from previous features being solid
|
||||
|
||||
## References
|
||||
|
||||
- [v0.2.0 System Overview](../v0.2.0/overview.md)
|
||||
- [v0.2.0 Data Model](../v0.2.0/data-model.md)
|
||||
- [Product Backlog](../../BACKLOG.md)
|
||||
- [Project Overview](../../PROJECT_OVERVIEW.md)
|
||||
- [ADR-0002: Authentication Strategy](../../decisions/0002-authentication-strategy.md)
|
||||
- [ADR-0003: Participant Session Scoping](../../decisions/0003-participant-session-scoping.md)
|
||||
|
||||
---
|
||||
|
||||
**End of Phase 3 Design Overview**
|
||||
1141
docs/designs/v0.3.0/participant-self-management.md
Normal file
1141
docs/designs/v0.3.0/participant-self-management.md
Normal file
File diff suppressed because it is too large
Load Diff
545
docs/designs/v0.3.0/test-plan.md
Normal file
545
docs/designs/v0.3.0/test-plan.md
Normal file
@@ -0,0 +1,545 @@
|
||||
# Test Plan - v0.3.0
|
||||
|
||||
**Version**: 0.3.0
|
||||
**Date**: 2025-12-22
|
||||
**Status**: Test Specification
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the comprehensive test plan for Phase 3 (Participant Self-Management). It covers unit tests, integration tests, and acceptance criteria for all user stories in scope.
|
||||
|
||||
## Test Pyramid
|
||||
|
||||
```
|
||||
╱╲
|
||||
╱ ╲ E2E Tests (Manual QA)
|
||||
╱────╲ - Full user journeys
|
||||
╱ ╲ - Cross-browser testing
|
||||
╱────────╲
|
||||
╱ ╲ Integration Tests (pytest)
|
||||
╱────────────╲ - Route handlers
|
||||
╱ ╲ - Database operations
|
||||
╱────────────────╲ - Email sending
|
||||
╱──────────────────╲
|
||||
Unit Tests (pytest)
|
||||
- Business logic functions
|
||||
- Form validation
|
||||
- State checks
|
||||
```
|
||||
|
||||
## Test Coverage Goals
|
||||
|
||||
- **Overall coverage**: 80%+ (maintain Phase 2 level)
|
||||
- **Business logic**: 95%+ (pure functions)
|
||||
- **Route handlers**: 80%+ (integration tests)
|
||||
- **Templates**: Manual testing (not measured by coverage)
|
||||
|
||||
## 1. Unit Tests
|
||||
|
||||
### 1.1 Participant Utility Functions
|
||||
|
||||
**File**: `tests/unit/test_participant_utils.py`
|
||||
|
||||
| Test Case | Description | Assertions |
|
||||
|-----------|-------------|------------|
|
||||
| `test_can_update_profile_draft_state` | Profile updates allowed in draft | `can_update_profile() == True` |
|
||||
| `test_can_update_profile_registration_open` | Profile updates allowed when open | `can_update_profile() == True` |
|
||||
| `test_can_update_profile_registration_closed` | Profile updates allowed when closed | `can_update_profile() == True` |
|
||||
| `test_can_update_profile_matched_state` | Profile updates blocked after matching | `can_update_profile() == False` |
|
||||
| `test_can_update_profile_completed_state` | Profile updates blocked when completed | `can_update_profile() == False` |
|
||||
| `test_can_withdraw_draft_state` | Withdrawal allowed in draft | `can_withdraw() == True` |
|
||||
| `test_can_withdraw_registration_open` | Withdrawal allowed when open | `can_withdraw() == True` |
|
||||
| `test_can_withdraw_registration_closed` | Withdrawal blocked when closed | `can_withdraw() == False` |
|
||||
| `test_can_withdraw_matched_state` | Withdrawal blocked after matching | `can_withdraw() == False` |
|
||||
| `test_can_withdraw_already_withdrawn` | Withdrawal blocked if already withdrawn | `can_withdraw() == False` |
|
||||
| `test_get_active_participants` | Returns only non-withdrawn participants | Count and names match |
|
||||
| `test_get_active_participants_empty` | Returns empty list when all withdrawn | `len(participants) == 0` |
|
||||
| `test_get_active_participants_ordered` | Participants ordered by name | Names in alphabetical order |
|
||||
| `test_is_withdrawn_true` | Detects withdrawn participant | `is_withdrawn() == True` |
|
||||
| `test_is_withdrawn_false` | Detects active participant | `is_withdrawn() == False` |
|
||||
|
||||
**Fixtures needed**:
|
||||
```python
|
||||
@pytest.fixture
|
||||
def exchange_factory(db):
|
||||
"""Factory for creating exchanges in different states."""
|
||||
def _create(state='draft'):
|
||||
exchange = Exchange(
|
||||
slug=generate_slug(),
|
||||
name='Test Exchange',
|
||||
budget='$25-50',
|
||||
max_participants=50,
|
||||
registration_close_date=datetime.utcnow() + timedelta(days=7),
|
||||
exchange_date=datetime.utcnow() + timedelta(days=14),
|
||||
timezone='UTC',
|
||||
state=state
|
||||
)
|
||||
db.session.add(exchange)
|
||||
db.session.commit()
|
||||
return exchange
|
||||
return _create
|
||||
|
||||
@pytest.fixture
|
||||
def participant_factory(db):
|
||||
"""Factory for creating participants."""
|
||||
def _create(exchange=None, withdrawn_at=None):
|
||||
if not exchange:
|
||||
exchange = Exchange(...) # Create default exchange
|
||||
db.session.add(exchange)
|
||||
|
||||
participant = Participant(
|
||||
exchange_id=exchange.id,
|
||||
name='Test Participant',
|
||||
email='test@example.com',
|
||||
gift_ideas='Test ideas',
|
||||
reminder_enabled=True,
|
||||
withdrawn_at=withdrawn_at
|
||||
)
|
||||
db.session.add(participant)
|
||||
db.session.commit()
|
||||
return participant
|
||||
return _create
|
||||
```
|
||||
|
||||
### 1.2 Withdrawal Service
|
||||
|
||||
**File**: `tests/unit/test_withdrawal_service.py`
|
||||
|
||||
| Test Case | Description | Assertions |
|
||||
|-----------|-------------|------------|
|
||||
| `test_withdraw_participant_success` | Happy path withdrawal | `withdrawn_at` is set, email called |
|
||||
| `test_withdraw_participant_already_withdrawn` | Raises error if already withdrawn | `WithdrawalError` raised |
|
||||
| `test_withdraw_participant_wrong_state_closed` | Raises error if registration closed | `WithdrawalError` with specific message |
|
||||
| `test_withdraw_participant_wrong_state_matched` | Raises error if already matched | `WithdrawalError` with specific message |
|
||||
| `test_withdraw_participant_database_error` | Handles DB error gracefully | `WithdrawalError` raised, rollback called |
|
||||
| `test_withdraw_participant_email_failure` | Continues if email fails | `withdrawn_at` set, error logged |
|
||||
|
||||
**Mocking strategy**:
|
||||
```python
|
||||
@pytest.fixture
|
||||
def mock_email_service(monkeypatch):
|
||||
"""Mock EmailService for withdrawal tests."""
|
||||
mock = Mock()
|
||||
monkeypatch.setattr('src.services.withdrawal.EmailService', lambda: mock)
|
||||
return mock
|
||||
```
|
||||
|
||||
### 1.3 Form Validation
|
||||
|
||||
**File**: `tests/unit/test_participant_forms.py`
|
||||
|
||||
| Test Case | Description | Assertions |
|
||||
|-----------|-------------|------------|
|
||||
| `test_profile_form_valid_data` | Valid name and gift ideas | `form.validate() == True` |
|
||||
| `test_profile_form_name_required` | Name is required | Validation error on name field |
|
||||
| `test_profile_form_name_too_long` | Name max 255 chars | Validation error on name field |
|
||||
| `test_profile_form_gift_ideas_optional` | Gift ideas can be empty | `form.validate() == True` |
|
||||
| `test_profile_form_gift_ideas_too_long` | Gift ideas max 10,000 chars | Validation error on gift_ideas field |
|
||||
| `test_reminder_form_boolean_field` | Accepts boolean value | `form.validate() == True` |
|
||||
| `test_withdraw_form_confirmation_required` | Confirmation required | Validation error on confirm field |
|
||||
| `test_withdraw_form_confirmation_true` | Accepts confirmation | `form.validate() == True` |
|
||||
|
||||
## 2. Integration Tests
|
||||
|
||||
### 2.1 Profile Update Tests
|
||||
|
||||
**File**: `tests/integration/test_profile_update.py`
|
||||
|
||||
| Test Case | Description | Setup | Action | Expected Result |
|
||||
|-----------|-------------|-------|--------|-----------------|
|
||||
| `test_profile_update_get_shows_form` | GET shows edit form | Auth'd participant | GET /participant/profile/edit | 200, form with current values |
|
||||
| `test_profile_update_post_success` | POST updates profile | Auth'd participant | POST with valid data | 302 redirect, flash success, DB updated |
|
||||
| `test_profile_update_name_change` | Name updates in DB | Auth'd participant | POST with new name | Participant.name updated |
|
||||
| `test_profile_update_gift_ideas_change` | Gift ideas update in DB | Auth'd participant | POST with new ideas | Participant.gift_ideas updated |
|
||||
| `test_profile_update_clears_whitespace` | Strips leading/trailing spaces | Auth'd participant | POST with " Name " | Stored as "Name" |
|
||||
| `test_profile_update_locked_after_matching` | Blocked when matched | Matched exchange | GET profile edit | 302 redirect, flash error |
|
||||
| `test_profile_update_form_validation_error` | Invalid data shows errors | Auth'd participant | POST with empty name | 200, form with errors |
|
||||
| `test_profile_update_csrf_required` | CSRF token required | Auth'd participant | POST without CSRF | 400 error |
|
||||
| `test_profile_update_requires_auth` | Auth required | No session | GET profile edit | 302 to login |
|
||||
| `test_profile_update_database_error` | Handles DB failure | Auth'd participant, mock DB error | POST valid data | Flash error, no DB change |
|
||||
|
||||
**Test helpers**:
|
||||
```python
|
||||
@pytest.fixture
|
||||
def participant_session(client, participant_factory):
|
||||
"""Create authenticated participant session."""
|
||||
participant = participant_factory()
|
||||
|
||||
with client.session_transaction() as session:
|
||||
session['user_id'] = participant.id
|
||||
session['user_type'] = 'participant'
|
||||
session['exchange_id'] = participant.exchange_id
|
||||
|
||||
return participant
|
||||
|
||||
def get_csrf_token(client, url='/participant/dashboard'):
|
||||
"""Extract CSRF token from page."""
|
||||
response = client.get(url)
|
||||
# Parse HTML and extract token
|
||||
return extract_csrf_token(response.data)
|
||||
```
|
||||
|
||||
### 2.2 Withdrawal Tests
|
||||
|
||||
**File**: `tests/integration/test_withdrawal.py`
|
||||
|
||||
| Test Case | Description | Setup | Action | Expected Result |
|
||||
|-----------|-------------|-------|--------|-----------------|
|
||||
| `test_withdrawal_get_shows_form` | GET shows confirmation page | Auth'd participant | GET /participant/withdraw | 200, form with warnings |
|
||||
| `test_withdrawal_post_success` | POST withdraws participant | Auth'd participant | POST with confirmation | 302 redirect, withdrawn_at set, session cleared |
|
||||
| `test_withdrawal_sends_email` | Email sent on withdrawal | Auth'd participant, mock email | POST with confirmation | Email service called |
|
||||
| `test_withdrawal_clears_session` | Session cleared after withdrawal | Auth'd participant | POST with confirmation | Session empty |
|
||||
| `test_withdrawal_redirects_to_public` | Redirects to registration page | Auth'd participant | POST with confirmation | Redirect to /exchange/{slug}/register |
|
||||
| `test_withdrawal_already_withdrawn` | Detects already withdrawn | Withdrawn participant | GET withdraw | Flash info, redirect |
|
||||
| `test_withdrawal_blocked_after_close` | Blocked when registration closed | Closed exchange | GET withdraw | Flash error, redirect to dashboard |
|
||||
| `test_withdrawal_blocked_after_matching` | Blocked when matched | Matched exchange | GET withdraw | Flash error, redirect to dashboard |
|
||||
| `test_withdrawal_requires_confirmation` | Confirmation checkbox required | Auth'd participant | POST without confirm=True | Form errors |
|
||||
| `test_withdrawal_csrf_required` | CSRF token required | Auth'd participant | POST without CSRF | 400 error |
|
||||
| `test_withdrawal_requires_auth` | Auth required | No session | GET withdraw | 302 to login |
|
||||
| `test_withdrawal_database_error` | Handles DB failure gracefully | Auth'd participant, mock DB error | POST valid data | Flash error, not withdrawn |
|
||||
|
||||
### 2.3 Reminder Preference Tests
|
||||
|
||||
**File**: `tests/integration/test_reminder_preferences.py`
|
||||
|
||||
| Test Case | Description | Setup | Action | Expected Result |
|
||||
|-----------|-------------|-------|--------|-----------------|
|
||||
| `test_update_preferences_enable` | Enable reminders | Auth'd participant (disabled) | POST reminder_enabled=True | Flash success, DB updated |
|
||||
| `test_update_preferences_disable` | Disable reminders | Auth'd participant (enabled) | POST reminder_enabled=False | Flash success, DB updated |
|
||||
| `test_update_preferences_csrf_required` | CSRF token required | Auth'd participant | POST without CSRF | 400 error |
|
||||
| `test_update_preferences_requires_auth` | Auth required | No session | POST preferences | 302 to login |
|
||||
| `test_update_preferences_database_error` | Handles DB failure | Auth'd participant, mock DB error | POST valid data | Flash error, no change |
|
||||
|
||||
### 2.4 Participant List Tests
|
||||
|
||||
**File**: `tests/integration/test_participant_list.py`
|
||||
|
||||
| Test Case | Description | Setup | Action | Expected Result |
|
||||
|-----------|-------------|-------|--------|-----------------|
|
||||
| `test_participant_list_shows_all_active` | Shows all active participants | 3 active participants | GET dashboard | All 3 names displayed |
|
||||
| `test_participant_list_excludes_withdrawn` | Hides withdrawn participants | 2 active, 1 withdrawn | GET dashboard | Only 2 names displayed |
|
||||
| `test_participant_list_shows_self_badge` | Marks current user | Auth'd participant | GET dashboard | "You" badge on own name |
|
||||
| `test_participant_list_ordered_by_name` | Alphabetical order | Participants: Zoe, Alice, Bob | GET dashboard | Order: Alice, Bob, Zoe |
|
||||
| `test_participant_list_count_excludes_withdrawn` | Count shows active only | 3 active, 1 withdrawn | GET dashboard | "Participants (3)" |
|
||||
| `test_participant_list_empty_state` | Message when alone | Only current participant | GET dashboard | "No other participants yet" |
|
||||
| `test_participant_list_requires_auth` | Auth required | No session | GET dashboard | 302 to login |
|
||||
|
||||
## 3. Acceptance Tests
|
||||
|
||||
### 3.1 Story 4.5: View Participant List (Pre-Matching)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- ✅ Participant list visible after logging in via magic link
|
||||
- ✅ Shows display names only
|
||||
- ✅ Does not show email addresses
|
||||
- ✅ Does not indicate any match information
|
||||
- ✅ Updates as new participants register
|
||||
|
||||
**Manual Test Steps**:
|
||||
|
||||
1. **Setup**: Create exchange, register 3 participants (Alice, Bob, Charlie)
|
||||
2. **Login as Alice**: Request magic link, login
|
||||
3. **View Dashboard**: Participant list shows "Bob" and "Charlie" (not Alice's own name in list)
|
||||
4. **Verify No Emails**: Inspect HTML, confirm no email addresses visible
|
||||
5. **Register New Participant (Dave)**: Have Dave register
|
||||
6. **Refresh Dashboard**: Dave now appears in Alice's participant list
|
||||
7. **Bob Withdraws**: Have Bob withdraw
|
||||
8. **Refresh Dashboard**: Bob no longer appears in participant list
|
||||
|
||||
**Expected Results**: Pass all criteria
|
||||
|
||||
### 3.2 Story 6.1: Update Profile
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- ✅ Edit option available when logged in
|
||||
- ✅ Can update name and gift ideas
|
||||
- ✅ Cannot change email (request admin help)
|
||||
- ✅ Only available before matching occurs
|
||||
- ✅ Confirmation after save
|
||||
|
||||
**Manual Test Steps**:
|
||||
|
||||
1. **Login as Participant**: Use magic link to access dashboard
|
||||
2. **Click "Edit Profile"**: Navigate to profile edit page
|
||||
3. **Verify Pre-Population**: Name and gift ideas fields show current values
|
||||
4. **Update Name**: Change name from "Alice" to "Alice Smith"
|
||||
5. **Update Gift Ideas**: Add new gift idea
|
||||
6. **Verify Email Not Editable**: Email field not present in form
|
||||
7. **Save Changes**: Submit form
|
||||
8. **Verify Success Message**: "Your profile has been updated successfully"
|
||||
9. **Verify Dashboard Updated**: Dashboard shows new name and gift ideas
|
||||
10. **Admin Matches Exchange**: Admin triggers matching
|
||||
11. **Try to Edit Profile**: Click "Edit Profile" (if visible)
|
||||
12. **Verify Locked**: Error message "Your profile is locked after matching"
|
||||
|
||||
**Expected Results**: Pass all criteria
|
||||
|
||||
### 3.3 Story 6.2: Withdraw from Exchange
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- ✅ "Withdraw" option available before registration closes
|
||||
- ✅ Confirmation required
|
||||
- ✅ Participant removed from exchange
|
||||
- ✅ Confirmation email sent
|
||||
- ✅ Admin notified (if notifications enabled) - **Deferred to Phase 7**
|
||||
|
||||
**Manual Test Steps**:
|
||||
|
||||
1. **Login as Participant**: Use magic link
|
||||
2. **Click "Withdraw from Exchange"**: Navigate to withdrawal page
|
||||
3. **Verify Warning**: Warning box shows consequences
|
||||
4. **Try Submit Without Confirmation**: Leave checkbox unchecked, submit
|
||||
5. **Verify Error**: Form error requires confirmation
|
||||
6. **Check Confirmation Box**: Check "I understand..." box
|
||||
7. **Submit Withdrawal**: Click "Withdraw from Exchange"
|
||||
8. **Verify Success Message**: "You have been withdrawn..."
|
||||
9. **Verify Logged Out**: Session cleared, redirected to public page
|
||||
10. **Check Email**: Withdrawal confirmation email received
|
||||
11. **Login as Different Participant**: Login as Bob
|
||||
12. **Check Participant List**: Withdrawn participant (Alice) not in list
|
||||
13. **Admin Closes Registration**: Admin closes registration
|
||||
14. **Try to Withdraw as Bob**: Navigate to withdrawal page
|
||||
15. **Verify Blocked**: Error message "Registration has closed"
|
||||
|
||||
**Expected Results**: Pass all criteria (except admin notification - Phase 7)
|
||||
|
||||
### 3.4 Story 6.3: Update Reminder Preferences
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- ✅ Option to enable/disable reminder emails
|
||||
- ✅ Available before exchange completes
|
||||
- ✅ Changes take effect immediately
|
||||
|
||||
**Manual Test Steps**:
|
||||
|
||||
1. **Login as Participant**: Use magic link
|
||||
2. **View Dashboard**: Reminder preference checkbox visible
|
||||
3. **Verify Current State**: Checkbox checked (enabled by default)
|
||||
4. **Uncheck Reminder Box**: Uncheck "Send me reminders"
|
||||
5. **Click "Update Preferences"**: Submit form
|
||||
6. **Verify Success Message**: "Reminder emails disabled"
|
||||
7. **Refresh Page**: Checkbox remains unchecked
|
||||
8. **Re-Enable Reminders**: Check box, submit
|
||||
9. **Verify Success Message**: "Reminder emails enabled"
|
||||
10. **Admin Matches Exchange**: Trigger matching
|
||||
11. **Verify Still Available**: Reminder preference form still available post-matching
|
||||
|
||||
**Expected Results**: Pass all criteria
|
||||
|
||||
## 4. Edge Cases and Error Scenarios
|
||||
|
||||
### 4.1 Race Conditions
|
||||
|
||||
| Scenario | Expected Behavior | Test Method |
|
||||
|----------|-------------------|-------------|
|
||||
| Participant withdraws while admin is matching | Withdrawal succeeds if submitted before matching, blocked if submitted after | Manual timing test |
|
||||
| Two participants update profiles simultaneously | Both succeed (no conflicts, different records) | Concurrent requests test |
|
||||
| Participant updates profile while admin closes registration | Both succeed (profile lock is at matching, not close) | Manual timing test |
|
||||
|
||||
### 4.2 Data Validation
|
||||
|
||||
| Scenario | Expected Behavior | Test Method |
|
||||
|----------|-------------------|-------------|
|
||||
| Name with only whitespace | Validation error "Name is required" | Integration test |
|
||||
| Gift ideas exactly 10,000 characters | Accepted | Integration test |
|
||||
| Gift ideas 10,001 characters | Validation error | Integration test |
|
||||
| Name with special characters (é, ñ, 中) | Accepted, displayed correctly | Manual test |
|
||||
| XSS attempt in gift ideas | Escaped in display | Manual test |
|
||||
|
||||
### 4.3 Session Handling
|
||||
|
||||
| Scenario | Expected Behavior | Test Method |
|
||||
|----------|-------------------|-------------|
|
||||
| Session expires during profile edit | Redirect to login on submit | Manual test (wait for expiry) |
|
||||
| Withdraw from different browser tab | Both tabs see withdrawal (one succeeds, one sees "already withdrawn") | Manual test |
|
||||
| Participant deleted by admin while logged in | Next request clears session, redirects to login | Integration test |
|
||||
|
||||
## 5. Performance Tests
|
||||
|
||||
### 5.1 Query Optimization
|
||||
|
||||
| Test | Query Count | Execution Time |
|
||||
|------|-------------|----------------|
|
||||
| Load dashboard with 50 participants | 3 queries max (exchange, participant, participant list) | < 100ms |
|
||||
| Update profile | 2 queries (load participant, update) | < 50ms |
|
||||
| Withdraw participant | 3 queries (load, update, email) | < 100ms |
|
||||
|
||||
**Testing Method**: Use Flask-DebugToolbar or SQLAlchemy query logging
|
||||
|
||||
### 5.2 Concurrent Operations
|
||||
|
||||
| Test | Concurrent Requests | Expected Result |
|
||||
|------|---------------------|-----------------|
|
||||
| 10 participants updating profiles | 10 simultaneous | All succeed, no deadlocks |
|
||||
| 5 participants viewing participant list | 5 simultaneous | All succeed, consistent data |
|
||||
|
||||
**Testing Method**: Use locust or manual concurrent curl requests
|
||||
|
||||
## 6. Browser Compatibility
|
||||
|
||||
**Supported Browsers** (per project requirements):
|
||||
- Chrome (last 2 versions)
|
||||
- Firefox (last 2 versions)
|
||||
- Safari (last 2 versions)
|
||||
- Edge (last 2 versions)
|
||||
|
||||
**Manual Tests**:
|
||||
- Profile edit form renders correctly
|
||||
- Character counter works (progressive enhancement)
|
||||
- Withdrawal confirmation checkbox works
|
||||
- CSRF tokens submitted correctly
|
||||
- Flash messages display correctly
|
||||
|
||||
## 7. Accessibility Tests
|
||||
|
||||
**WCAG 2.1 AA Compliance**:
|
||||
|
||||
| Test | Tool | Expected Result |
|
||||
|------|------|-----------------|
|
||||
| Form labels | axe DevTools | All inputs have associated labels |
|
||||
| Keyboard navigation | Manual | All actions accessible via keyboard |
|
||||
| Screen reader | NVDA/JAWS | Forms and messages announced correctly |
|
||||
| Color contrast | axe DevTools | All text meets 4.5:1 contrast ratio |
|
||||
| Error messages | Manual | Errors linked to fields via ARIA |
|
||||
|
||||
## 8. Security Tests
|
||||
|
||||
### 8.1 Authentication
|
||||
|
||||
| Test | Method | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| Access profile edit without login | GET /participant/profile/edit | 302 redirect to login |
|
||||
| Access withdrawal without login | GET /participant/withdraw | 302 redirect to login |
|
||||
| Use expired session | Set session to past timestamp | Session cleared, redirect to login |
|
||||
|
||||
### 8.2 Authorization
|
||||
|
||||
| Test | Method | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| Edit another participant's profile | Manipulate user_id in session | Decorator uses session, not URL param - not vulnerable |
|
||||
| Withdraw another participant | Manipulate user_id in session | Decorator uses session, not URL param - not vulnerable |
|
||||
|
||||
### 8.3 Input Validation
|
||||
|
||||
| Test | Method | Expected Result |
|
||||
|------|--------|-----------------|
|
||||
| SQL injection in name field | Submit `'; DROP TABLE participants; --` | Escaped, no SQL execution |
|
||||
| XSS in gift ideas | Submit `<script>alert('XSS')</script>` | Escaped, rendered as text |
|
||||
| CSRF attack | POST without token | 400 error |
|
||||
|
||||
## 9. Test Automation
|
||||
|
||||
### 9.1 CI Pipeline
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml (example)
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install uv
|
||||
run: pip install uv
|
||||
- name: Install dependencies
|
||||
run: uv sync
|
||||
- name: Run unit tests
|
||||
run: uv run pytest tests/unit -v --cov
|
||||
- name: Run integration tests
|
||||
run: uv run pytest tests/integration -v --cov --cov-append
|
||||
- name: Coverage report
|
||||
run: uv run coverage report --fail-under=80
|
||||
```
|
||||
|
||||
### 9.2 Pre-Commit Hooks
|
||||
|
||||
```yaml
|
||||
# .pre-commit-config.yaml
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pytest
|
||||
name: pytest
|
||||
entry: uv run pytest tests/unit
|
||||
language: system
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
```
|
||||
|
||||
## 10. Test Data Management
|
||||
|
||||
### 10.1 Fixtures
|
||||
|
||||
**Location**: `tests/conftest.py`
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
def app():
|
||||
"""Create test Flask app."""
|
||||
app = create_app('testing')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
db.drop_all()
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
"""Create test client."""
|
||||
return app.test_client()
|
||||
|
||||
@pytest.fixture
|
||||
def exchange(db):
|
||||
"""Create test exchange."""
|
||||
exchange = Exchange(...)
|
||||
db.session.add(exchange)
|
||||
db.session.commit()
|
||||
return exchange
|
||||
|
||||
@pytest.fixture
|
||||
def participant(db, exchange):
|
||||
"""Create test participant."""
|
||||
participant = Participant(exchange=exchange, ...)
|
||||
db.session.add(participant)
|
||||
db.session.commit()
|
||||
return participant
|
||||
```
|
||||
|
||||
### 10.2 Test Database
|
||||
|
||||
- Use in-memory SQLite for speed: `sqlite:///:memory:`
|
||||
- Reset between tests: `db.drop_all()` + `db.create_all()`
|
||||
- Factories for generating test data with variations
|
||||
|
||||
## 11. Regression Tests
|
||||
|
||||
Tests from Phase 2 that must still pass:
|
||||
|
||||
- ✅ Participant registration still works
|
||||
- ✅ Magic link authentication still works
|
||||
- ✅ Participant dashboard loads (now with participant list)
|
||||
- ✅ Admin login still works
|
||||
- ✅ Exchange creation still works
|
||||
|
||||
## 12. Success Criteria
|
||||
|
||||
Phase 3 tests are complete when:
|
||||
|
||||
1. ✅ All unit tests pass (100% pass rate)
|
||||
2. ✅ All integration tests pass (100% pass rate)
|
||||
3. ✅ Code coverage ≥ 80%
|
||||
4. ✅ All acceptance criteria manually verified
|
||||
5. ✅ No security vulnerabilities found
|
||||
6. ✅ Accessibility tests pass
|
||||
7. ✅ All edge cases handled gracefully
|
||||
8. ✅ Performance benchmarks met
|
||||
9. ✅ Browser compatibility verified
|
||||
10. ✅ Phase 2 regression tests pass
|
||||
|
||||
---
|
||||
|
||||
**Test Plan Version**: 1.0
|
||||
**Last Updated**: 2025-12-22
|
||||
**Status**: Ready for Implementation
|
||||
Reference in New Issue
Block a user