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:
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