Skip to content

PiqueTickets API Checkout Testing Plan

Document Version: 1.0 Created: 2025-10-27 Status: Ready for Review Related Issues: PIQUE-573 (Race Condition Fix)


Table of Contents

  1. Executive Summary
  2. Checkout Architecture Overview
  3. Current Test Coverage Analysis
  4. Test Coverage Gaps
  5. Comprehensive Test Plan
  6. Implementation Strategy
  7. Test Data & Fixtures
  8. CI/CD Integration
  9. Appendices

Executive Summary

Purpose

This document outlines a comprehensive testing strategy for the PiqueTickets API checkout process, focusing on ensuring system reliability, data integrity, and race condition prevention.

Key Findings

Existing Coverage: - ✅ Basic order creation and validation (test_order_view.py) - ✅ Race condition prevention for concurrent purchases (test_order_race_condition.py) - ✅ Promo code validation and application - ✅ Lock timeout and safety mechanisms

Critical Gaps Identified: - ⚠️ Payment failure scenarios and rollback verification - ⚠️ Network timeout handling during Stripe API calls - ⚠️ Database connection failures mid-transaction - ⚠️ Redis cache unavailability scenarios - ⚠️ Complex edge cases (price changes, expired sessions, etc.) - ⚠️ Load testing and performance benchmarks - ⚠️ End-to-end integration tests with actual Stripe test mode

Recommendations

Priority Levels: - P0 (Critical): Tests that must pass for checkout to be considered functional - P1 (High): Important edge cases that could cause data integrity issues - P2 (Medium): Performance, monitoring, and nice-to-have coverage

Estimated Implementation Time: 40-60 hours for complete coverage


Checkout Architecture Overview

Components

┌─────────────────────────────────────────────────────────────────┐
│                     Checkout Flow Overview                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. CheckoutSessionView (order_views.py)                        │
│     ├─ Request Validation (order_validation.py)                │
│     ├─ Show & Promo Code Validation                            │
│     ├─ Redis Lock Acquisition (120s timeout)                   │
│     ├─ Database Transaction (atomic)                           │
│     │   ├─ Row-Level Locks (select_for_update)                 │
│     │   ├─ Ticket Availability Check                           │
│     │   ├─ Fee Calculation                                     │
│     │   ├─ Order Creation                                      │
│     │   └─ TicketOrder Creation                                │
│     ├─ Lock Release (finally block)                            │
│     └─ Stripe Session Creation / Free Order Handling           │
│                                                                  │
│  2. SuccessSessionView (order_views.py)                        │
│     ├─ Order Completion                                        │
│     ├─ Attendee Creation                                       │
│     ├─ Email Notifications (Celery)                            │
│     ├─ PDF Ticket Generation (Celery)                          │
│     ├─ Show Reminder Scheduling (Celery)                       │
│     └─ Stripe Metadata Updates                                 │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Data Models

Order - Customer information (name, email, phone) - Payment details (session_id, payment_intent_id, charge_id) - Financial tracking (total, platform_fees, payment_processing_fees) - Promo code tracking (promo_code, promo_code_discount) - Status (success, is_manual_order)

TicketOrder - Ticket reference and quantity - Pricing at time of purchase (price_per_ticket, total_price) - Discount tracking (discount_amount, promo_code) - Donation tracking (donation_amount) - VIP status (is_vip) - Attendee relationships

Ticket - Name, description, price - Inventory management (quantity) - Donation-based pricing support (is_donation_based) - Show relationship

TicketPromoCode - Code (auto-uppercased) - Discount percentage (0.00-1.00) - Show-specific

External Integrations

Stripe - Checkout Sessions - Payment Intents - Connected Accounts (Producer transfers) - Metadata tracking

Redis - Distributed locking mechanism - 120-second timeout - UUID-based lock values

Celery - Email notifications - PDF ticket generation - Show reminder scheduling


Current Test Coverage Analysis

Test Files Overview

1. test_order_view.py (734 lines)

TestOrderView Class: - ✅ Create order with regular tickets - ✅ Create order with donation tickets - ✅ Create order with mixed tickets - ✅ Invalid ticket quantity handling - ✅ Invalid donation amount handling - ✅ Sold out tickets handling - ✅ Missing required fields - ✅ Invalid email format - ✅ Past show date validation - ✅ Valid promo code application - ✅ Invalid promo code handling - ✅ Promo code with mixed tickets - ✅ Promo code with free tickets - ✅ Promo code discount limits (100% discount) - ✅ Failed orders don't affect remaining tickets

TestOrderValidations Class: - ✅ Name validation (valid names, special characters, length limits) - ✅ Show validation (existence, published status, past shows) - ✅ Quantity validation (positive integers, invalid formats) - ✅ Donation validation (non-negative, within limits) - ✅ Request parameters validation - ✅ Producer financial validation - ✅ Promo code validation (case sensitivity, show-specific)

2. test_order_race_condition.py (591 lines)

TicketPurchaseRaceConditionTest Class: - ✅ Concurrent purchase prevents overselling - ✅ Lock timeout extended to 120 seconds - ✅ Lock uses unique UUID values - ✅ Lock released after successful transaction - ✅ Lock released after failed transaction - ✅ Multiple tickets all locks acquired - ✅ Lock contention metrics logged - ✅ Stripe call happens outside locked section

LockSafetyTest Class: - ✅ Lock value prevents accidental release - ✅ Expired lock handling

Coverage Metrics (Estimated)

Component Coverage % Notes
Order Creation 85% Good coverage of happy path and basic validations
Promo Codes 90% Comprehensive promo code testing
Race Conditions 95% Excellent lock mechanism coverage
Payment Processing 40% Mocked Stripe, minimal failure scenarios
Error Recovery 30% Limited database/network failure testing
Performance 10% No load testing or benchmarks
End-to-End 20% Mostly unit tests, few integration tests

Test Coverage Gaps

Critical Gaps (P0)

  1. Payment Processing Failures
  2. Stripe API timeout during checkout
  3. Payment declined scenarios
  4. Network failures during payment processing
  5. Webhook delivery failures

  6. Database Transaction Failures

  7. Database connection loss mid-transaction
  8. Query timeout scenarios
  9. Deadlock handling
  10. Transaction rollback verification

  11. Data Integrity

  12. Negative inventory prevention
  13. Orphaned transaction cleanup
  14. Order total calculation accuracy
  15. Tax calculation verification (automatic tax enabled)

High Priority Gaps (P1)

  1. Complex Edge Cases
  2. Price changes between cart view and checkout
  3. Show cancellation during checkout
  4. Ticket deletion during active checkout
  5. Session expiration scenarios
  6. Concurrent promo code usage and limits
  7. Mixed currency handling (if applicable)

  8. External Service Failures

  9. Redis unavailability during lock acquisition
  10. Celery task queue failures
  11. Email delivery failures
  12. PDF generation failures

  13. Security & Validation

  14. SQL injection attempts in checkout fields
  15. CSRF token validation
  16. Rate limiting under attack
  17. Maximum order amount enforcement
  18. XSS in customer input fields

Medium Priority Gaps (P2)

  1. Performance & Load
  2. Concurrent checkout capacity (100+ users)
  3. Database connection pool exhaustion
  4. Lock contention under high load
  5. Response time benchmarks (p50, p95, p99)
  6. Memory leak detection

  7. User Experience

  8. Clear error messages for all failure modes
  9. Idempotency key handling for retries
  10. Browser back button handling
  11. Multiple tab scenarios

Comprehensive Test Plan

Test Categories by Priority


P0: Critical - Core Checkout Functionality

These tests MUST pass for checkout to be considered functional.

P0-001: Successful Single Ticket Purchase

Test Name: test_single_ticket_purchase_success Priority: P0 Description: Verify complete flow for purchasing one ticket Prerequisites: - Published show with available tickets - Valid customer information - Producer with Stripe account configured

Test Steps: 1. Create checkout session with single ticket 2. Verify Stripe session created 3. Simulate successful Stripe payment 4. Call success callback 5. Verify order marked as successful 6. Verify attendee created 7. Verify ticket inventory decremented

Expected Outcomes: - Order created with success=True - One TicketAttendee record created - Ticket remaining count reduced by 1 - Email notification queued - PDF generation queued

Mocking Strategy: - Mock Stripe API calls - Mock Celery tasks

Dependencies: None


P0-002: Successful Multiple Ticket Purchase

Test Name: test_multiple_tickets_purchase_success Priority: P0 Description: Verify purchasing multiple tickets in one order Test Steps: 1. Create checkout with 2 different ticket types, 2 quantity each 2. Complete purchase flow 3. Verify all tickets and attendees created

Expected Outcomes: - 4 total attendees created - Both ticket types inventory decremented correctly - Total calculated correctly with fees


P0-003: Free Ticket Purchase

Test Name: test_free_ticket_purchase_no_stripe Priority: P0 Description: Verify free tickets don't require Stripe Test Steps: 1. Create ticket with $0 price 2. Attempt checkout 3. Verify no Stripe session created 4. Verify order completed immediately

Expected Outcomes: - Order created with session_id FREE-{order_id} - No Stripe API call - No fees charged - Order marked successful


P0-004: Prevent Overselling (Last Ticket)

Test Name: test_last_ticket_purchase_atomicity Priority: P0 Description: Verify that attempting to purchase more tickets than available fails Test Steps: 1. Create ticket with quantity=1 2. Attempt to purchase quantity=2 3. Verify error returned

Expected Outcomes: - 400 Bad Request - Error message indicates unavailability - No order created


P0-005: Sold Out Ticket Rejection

Test Name: test_sold_out_ticket_rejection Priority: P0 Description: Verify sold out tickets cannot be purchased Prerequisites: - Ticket with all inventory allocated to successful orders

Test Steps: 1. Create successful order consuming all inventory 2. Attempt another purchase 3. Verify rejection

Expected Outcomes: - 400 Bad Request - Clear "not available" message - Ticket inventory unchanged


P0-006: Order Total Calculation Accuracy

Test Name: test_order_total_calculation_with_fees Priority: P0 Description: Verify correct calculation of totals including all fees Test Steps: 1. Purchase 2 tickets at $25 each 2. Verify platform fees: 2 × $1.50 = $3.00 3. Verify processing fees: ((50 + 3) × 0.029) + 0.30 = $1.84 4. Verify total: $50 + $3 + $1.84 = $54.84

Expected Outcomes: - Order.total = $54.84 - Order.platform_fees = $3.00 - Order.payment_processing_fees = $1.84 - Stripe line items match exactly


P0-007: Promo Code Discount Applied Correctly

Test Name: test_promo_code_discount_calculation Priority: P0 Description: Verify promo codes discount tickets correctly, not fees Prerequisites: - Promo code with 20% discount

Test Steps: 1. Purchase $100 worth of tickets 2. Apply 20% promo code 3. Verify discount: $20 off tickets 4. Verify fees calculated on $80 (discounted amount)

Expected Outcomes: - Ticket subtotal: $80 - Platform fees: still calculated per ticket - Processing fees: calculated on ($80 + platform_fees) - Order.promo_code_discount = $20


P0-008: Email and Phone Validation

Test Name: test_customer_info_validation Priority: P0 Description: Verify customer information is properly validated Test Steps: 1. Attempt checkout with invalid email 2. Attempt checkout with overly long name 3. Attempt checkout with special characters in name

Expected Outcomes: - Invalid email: 400 error with "Invalid email format" - Long name: 400 error with character limit message - Special characters: Accept hyphens, apostrophes, spaces; reject numbers/symbols


P0-009: Negative Quantity Prevention

Test Name: test_negative_quantity_rejection Priority: P0 Description: Prevent negative or zero quantity orders Test Steps: 1. Attempt checkout with quantity=-1 2. Attempt checkout with quantity=0

Expected Outcomes: - Negative quantity: 400 error - Zero quantity: Skipped (no order created for that ticket)


P0-010: Maximum Tickets Per Order Enforcement

Test Name: test_max_tickets_per_order_limit Priority: P0 Description: Enforce MAX_TICKETS_PER_ORDER (currently 10) Test Steps: 1. Attempt to purchase 11 tickets 2. Verify rejection

Expected Outcomes: - 400 error with "Maximum 10 tickets per order" - No order created


P1: High Priority - Important Edge Cases

These tests cover important scenarios that could cause data integrity issues.

P1-001: Payment Declined by Stripe

Test Name: test_payment_declined_order_not_completed Priority: P1 Description: Verify orders aren't marked successful if payment declines Test Steps: 1. Create checkout session 2. Simulate Stripe payment declined 3. Verify order remains with success=False 4. Verify ticket inventory NOT decremented

Expected Outcomes: - Order exists but success=False - Inventory unchanged - No attendees created - No emails sent


P1-002: Stripe API Timeout During Checkout

Test Name: test_stripe_timeout_handling Priority: P1 Description: Handle Stripe API timeouts gracefully Test Steps: 1. Mock Stripe to timeout 2. Attempt checkout 3. Verify error handling

Expected Outcomes: - 500 or 503 error with retry message - Order may be created but not completed - Locks released properly


P1-003: Database Connection Failure Mid-Transaction

Test Name: test_database_failure_during_order_creation Priority: P1 Description: Verify proper rollback on database failure Test Steps: 1. Mock database connection failure during order creation 2. Attempt checkout 3. Verify rollback

Expected Outcomes: - No partial order created - Inventory unchanged - Locks released - Clear error message


P1-004: Redis Unavailable During Lock Acquisition

Test Name: test_redis_unavailable_graceful_degradation Priority: P1 Description: Handle Redis unavailability Test Steps: 1. Disable Redis cache 2. Attempt checkout 3. Verify behavior

Expected Outcomes: - Either: graceful degradation to database-only locking - Or: clear error message about system unavailability - No data corruption


P1-005: Lock Expiry During Long Transaction

Test Name: test_lock_expiry_warning_on_slow_transaction Priority: P1 Description: Test behavior when transaction exceeds 120s Test Steps: 1. Mock slow database queries (>120s) 2. Attempt checkout 3. Verify lock expiry handling

Expected Outcomes: - Transaction should complete (database locks still held) - Warning logged about lock expiry - No race condition (database locks prevent)


P1-006: Concurrent Promo Code Usage Limit

Test Name: test_promo_code_usage_limits Priority: P1 Description: If promo codes have usage limits, test enforcement Prerequisites: - Promo code with max_uses (if implemented)

Test Steps: 1. Use promo code up to limit 2. Attempt to use again 3. Verify rejection

Expected Outcomes: - Rejection with "Promo code no longer valid" - Usage count incremented atomically

Note: Currently not implemented, add if feature exists


P1-007: Price Change Between View and Checkout

Test Name: test_price_change_during_checkout Priority: P1 Description: Verify price at checkout time is used, not cached price Test Steps: 1. View ticket with price $25 2. Admin changes price to $30 3. Attempt checkout 4. Verify $30 price used

Expected Outcomes: - TicketOrder.price_per_ticket reflects current price ($30) - No stale price used


P1-008: Show Unpublished During Checkout

Test Name: test_show_unpublished_during_checkout Priority: P1 Description: Handle show being unpublished mid-checkout Test Steps: 1. Start checkout for published show 2. Admin unpublishes show 3. Complete checkout attempt 4. Verify rejection

Expected Outcomes: - 400 error "Show not currently available" - No order created


P1-009: Ticket Deleted During Active Checkout

Test Name: test_ticket_deleted_during_checkout Priority: P1 Description: Handle ticket deletion during checkout Test Steps: 1. Acquire lock for ticket 2. Admin attempts to delete ticket (should wait or fail) 3. Complete or cancel checkout 4. Verify integrity

Expected Outcomes: - Ticket deletion waits for lock release (select_for_update) - Or deletion fails with lock held - No orphaned references


P1-010: Order Success Callback Fired Twice

Test Name: test_duplicate_success_callback_idempotency Priority: P1 Description: Verify idempotency of success callback Test Steps: 1. Complete order successfully 2. Call success callback again with same session_id 3. Verify no duplicate processing

Expected Outcomes: - Second callback does nothing (order.success already True) - No duplicate attendees created - No duplicate emails sent - Redirects to show page


P1-011: Maximum Order Amount Enforcement

Test Name: test_max_order_amount_limit Priority: P1 Description: Enforce MAX_ORDER_AMOUNT ($5000) Test Steps: 1. Create high-priced tickets 2. Attempt order totaling >$5000 3. Verify rejection

Expected Outcomes: - 400 error "Order total exceeds maximum amount of $5000" - No order created


P1-012: Donation Amount Limits

Test Name: test_donation_amount_limits Priority: P1 Description: Enforce MAX_DONATION_PER_TICKET ($1000) Test Steps: 1. Attempt donation of $1001 2. Verify rejection

Expected Outcomes: - 400 error with donation limit message


P1-013: Donation Below Minimum for Donation Tickets

Test Name: test_donation_below_minimum_rejected Priority: P1 Description: Donation tickets require at least the base price Prerequisites: - Donation ticket with base price $10

Test Steps: 1. Attempt donation of $5 2. Verify rejection

Expected Outcomes: - 400 error "Donation amount must be at least $10"


P1-014: Non-Donation Tickets Cannot Accept Donations

Test Name: test_regular_ticket_no_donation_accepted Priority: P1 Description: Regular tickets cannot have additional donations Test Steps: 1. Attempt to add donation to regular ticket 2. Verify rejection

Expected Outcomes: - 400 error "Additional donations not allowed for non-donation ticket"


P1-015: Past Show Rejection

Test Name: test_past_show_checkout_rejection Priority: P1 Description: Cannot purchase tickets for shows that have ended Test Steps: 1. Set show end_time to past 2. Attempt checkout 3. Verify rejection

Expected Outcomes: - 400 error "This show has already occurred" - No order created


P1-016: Unpublished Show Rejection

Test Name: test_unpublished_show_checkout_rejection Priority: P1 Description: Cannot purchase tickets for unpublished shows Test Steps: 1. Set show published=False 2. Attempt checkout 3. Verify rejection

Expected Outcomes: - 400 error "This show is not currently available"


P1-017: Producer Without Stripe Account

Test Name: test_producer_no_stripe_account_rejection Priority: P1 Description: Verify paid tickets require producer Stripe account Test Steps: 1. Remove producer Stripe account 2. Attempt to purchase paid tickets 3. Verify rejection

Expected Outcomes: - 400 error "Producer payment details not configured" - Free tickets should still work


P1-018: Partial Order Failure Rollback

Test Name: test_partial_order_rollback_on_error Priority: P1 Description: If one ticket in multi-ticket order fails, roll back entire order Test Steps: 1. Attempt to purchase 2 ticket types 2. Mock failure on second ticket 3. Verify rollback

Expected Outcomes: - No order created - First ticket inventory unchanged - Atomic transaction enforced


P1-019: Stripe Transfer Metadata Accuracy

Test Name: test_stripe_transfer_metadata_populated Priority: P1 Description: Verify Stripe transfer includes correct metadata Test Steps: 1. Complete successful order 2. Verify Stripe transfer metadata includes: - order_id - show_id - show_title - attendee_count - order_name - order_email

Expected Outcomes: - All metadata fields populated - Transfer description accurate


P1-020: Automatic Tax Calculation

Test Name: test_automatic_tax_calculation Priority: P1 Description: Verify Stripe automatic tax is enabled Test Steps: 1. Create checkout session 2. Verify automatic_tax: {enabled: true} in Stripe session

Expected Outcomes: - Tax calculation delegated to Stripe - Tax amount not in our fee calculation


P2: Medium Priority - Performance & Resilience

These tests cover performance, monitoring, and nice-to-have coverage.

P2-001: Concurrent Checkout Load Test

Test Name: test_100_concurrent_checkouts Priority: P2 Description: Load test with 100 concurrent users Test Steps: 1. Create show with 100 tickets 2. Spawn 100 threads each attempting checkout 3. Verify all complete successfully 4. Measure response times

Expected Outcomes: - All 100 orders complete - p95 response time < 10 seconds - No lock timeouts - No database connection pool exhaustion

Tools: pytest-xdist, locust, or similar


P2-002: Database Connection Pool Exhaustion

Test Name: test_connection_pool_under_load Priority: P2 Description: Test behavior when connection pool exhausted Test Steps: 1. Reduce connection pool size 2. Generate high load 3. Verify graceful handling

Expected Outcomes: - Requests queue or fail gracefully - No crashes - Connections released after transactions


P2-003: Lock Contention Performance

Test Name: test_lock_wait_time_under_contention Priority: P2 Description: Measure lock wait time during contention Test Steps: 1. 50 concurrent attempts for same ticket 2. Measure time each waits for lock 3. Verify reasonable performance

Expected Outcomes: - Lock acquisition p95 < 1 second - Proper queuing behavior - No starvation


P2-004: Memory Leak Detection

Test Name: test_no_memory_leaks_over_1000_orders Priority: P2 Description: Verify no memory leaks in checkout flow Test Steps: 1. Measure baseline memory 2. Process 1000 orders 3. Force garbage collection 4. Measure final memory

Expected Outcomes: - Memory growth < 10MB - No circular references - Proper cleanup

Tools: memory_profiler, tracemalloc


P2-005: Celery Task Failure Recovery

Test Name: test_celery_task_failures_dont_block_checkout Priority: P2 Description: Verify checkout succeeds even if email/PDF tasks fail Test Steps: 1. Mock Celery task failures 2. Complete checkout 3. Verify order still successful

Expected Outcomes: - Order completed - Errors logged but not raised - Tasks can be retried later


P2-006: Browser Back Button After Success

Test Name: test_back_button_after_success_idempotency Priority: P2 Description: Verify using back button after success is safe Test Steps: 1. Complete successful checkout 2. Simulate back button (revisit checkout endpoint) 3. Verify no duplicate processing

Expected Outcomes: - Redirect or error (order already exists) - No duplicate charge


P2-007: Multiple Tabs Same Checkout

Test Name: test_multiple_tabs_same_checkout_race Priority: P2 Description: User opens checkout in multiple tabs Test Steps: 1. Initiate checkout in tab 1 2. Acquire lock 3. Attempt checkout in tab 2 4. Verify proper handling

Expected Outcomes: - One succeeds, one gets "being processed" error - No duplicate orders


P2-008: Long-Running Transaction Monitoring

Test Name: test_transaction_duration_logging Priority: P2 Description: Verify transaction duration is logged Test Steps: 1. Complete checkout 2. Verify logs include: - "Lock acquired for tickets..." - "Transaction completed in X.XXs" - "Order {id} created in X.XXs"

Expected Outcomes: - All timing logs present - Can monitor for slow transactions


P2-009: Stripe Webhook Replay Attack Prevention

Test Name: test_stripe_webhook_signature_validation Priority: P2 Description: Verify webhook signature validation (if implemented) Prerequisites: - Stripe webhook handler exists

Test Steps: 1. Send webhook with invalid signature 2. Verify rejection

Expected Outcomes: - Webhook rejected - No order processing

Note: May not be implemented yet


P2-010: Rate Limiting Under Attack

Test Name: test_checkout_rate_limiting Priority: P2 Description: Verify rate limiting prevents abuse Prerequisites: - Rate limiting configured

Test Steps: 1. Make 100 rapid checkout attempts 2. Verify rate limiting kicks in

Expected Outcomes: - 429 Too Many Requests after threshold - System remains stable

Note: May not be implemented yet


P2-011: SQL Injection Attempt in Checkout

Test Name: test_sql_injection_prevention Priority: P2 Description: Verify Django ORM prevents SQL injection Test Steps: 1. Submit checkout with SQL in name: '; DROP TABLE orders; -- 2. Verify no execution

Expected Outcomes: - Stored as literal string - No SQL execution - Order created safely


P2-012: XSS Attempt in Customer Fields

Test Name: test_xss_prevention_in_customer_input Priority: P2 Description: Verify XSS payloads are sanitized Test Steps: 1. Submit name with <script>alert('xss')</script> 2. Retrieve order 3. Verify script not executed

Expected Outcomes: - Stored safely - HTML escaped on display - No script execution


P2-013: CSRF Token Validation

Test Name: test_csrf_protection_on_checkout Priority: P2 Description: Verify CSRF protection (if POST) Note: Current implementation uses GET, consider if this should be POST

Expected Outcomes: - CSRF token required if POST - Invalid token rejected


P2-014: Orphaned Order Cleanup

Test Name: test_abandoned_order_cleanup Priority: P2 Description: Test cleanup of orders that were never completed Prerequisites: - Cleanup task exists

Test Steps: 1. Create order with success=False 2. Wait beyond cleanup period 3. Run cleanup task 4. Verify order handled appropriately

Expected Outcomes: - Old failed orders archived or deleted - Inventory released

Note: May not be implemented yet


P2-015: Stripe Idempotency Key Usage

Test Name: test_stripe_idempotency_key_on_retry Priority: P2 Description: Verify Stripe calls use idempotency keys Test Steps: 1. Mock network failure on first Stripe call 2. Retry 3. Verify idempotency key used

Expected Outcomes: - Same idempotency key on retry - No duplicate charges

Note: May not be implemented yet


Test Coverage Summary by Category

Category P0 Tests P1 Tests P2 Tests Total
Core Checkout 10 0 0 10
Edge Cases 0 20 0 20
Performance 0 0 7 7
Security 0 0 3 3
Resilience 0 0 5 5
Total 10 20 15 45

Existing Coverage: ~15 tests (test_order_view.py + test_order_race_condition.py) New Tests Needed: ~30 tests Total Comprehensive Coverage: ~45 tests


Implementation Strategy

Phase 1: P0 Tests (Week 1)

Goal: Ensure all critical functionality is covered

Tasks: 1. Implement P0-001 through P0-010 (10 tests) 2. Ensure all tests pass with current implementation 3. Document any bugs found

Estimated Time: 16-20 hours

Deliverables: - test_checkout_core.py with all P0 tests - Bug reports for any issues found


Phase 2: P1 Tests (Week 2-3)

Goal: Cover important edge cases and failure scenarios

Tasks: 1. Implement P1-001 through P1-020 (20 tests) 2. May require adding mocking infrastructure 3. May uncover bugs in error handling

Estimated Time: 24-32 hours

Deliverables: - test_checkout_edge_cases.py with all P1 tests - test_checkout_failures.py for failure scenarios - Error handling improvements (if needed)


Phase 3: P2 Tests (Week 4)

Goal: Performance, security, and resilience testing

Tasks: 1. Implement P2-001 through P2-015 (15 tests) 2. Set up load testing infrastructure 3. Add security testing tools 4. Performance benchmarking

Estimated Time: 20-28 hours

Deliverables: - test_checkout_performance.py for load tests - test_checkout_security.py for security tests - Performance baselines documented


Testing Framework & Tools

Primary Framework: pytest with Django

Required Plugins:

pytest>=7.4.0
pytest-django>=4.5.0
pytest-cov>=4.1.0  # Coverage reporting
pytest-xdist>=3.3.1  # Parallel test execution
pytest-timeout>=2.1.0  # Timeout handling
pytest-mock>=3.11.1  # Enhanced mocking

Additional Tools:

factory-boy>=3.3.0  # Test data factories
faker>=19.0.0  # Fake data generation
freezegun>=1.2.2  # Time mocking
responses>=0.23.0  # HTTP mocking
locust>=2.15.0  # Load testing (P2)
memory-profiler>=0.61.0  # Memory profiling (P2)

Mocking Strategy:

  1. Stripe API:

    from unittest.mock import patch, MagicMock
    
    @patch('tickets.views.order_views.stripe.checkout.Session.create')
    def test_checkout(self, mock_stripe):
        mock_stripe.return_value = MagicMock(url='https://checkout.stripe.com/test')
        # Test code
    

  2. Redis Cache:

    from django.test import override_settings
    
    TEST_CACHE_SETTINGS = {
        'default': {
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
            'LOCATION': 'test-cache',
        }
    }
    
    @override_settings(CACHES=TEST_CACHE_SETTINGS)
    class TestCheckout(TransactionTestCase):
        ...
    

  3. Celery Tasks:

    from unittest.mock import patch
    
    @patch('tickets.task.send_ticket_purchased_email.apply_async')
    def test_email_queued(self, mock_email):
        # Test code
        mock_email.assert_called_once()
    


Test Data & Fixtures

Shared Fixtures (conftest.py)

import pytest
from decimal import Decimal
from datetime import timedelta
from django.utils import timezone

@pytest.fixture
def test_producer():
    """Create a test producer with Stripe account."""
    from producers.models import Producer, ProducerFinancial

    producer = Producer.objects.create(name="Test Producer")
    ProducerFinancial.objects.create(
        producer=producer,
        stripe_account_id="acct_test123",
        stripe_account_link="https://test.stripe.com/acct_test123",
    )
    return producer

@pytest.fixture
def test_show(test_producer):
    """Create a test show."""
    from tickets.models import Show

    show_time = timezone.now() + timedelta(days=7)
    return Show.objects.create(
        title="Test Show",
        description="A test show",
        door_time=show_time,
        start_time=show_time + timedelta(hours=1),
        end_time=show_time + timedelta(hours=3),
        published=True,
        producer=test_producer,
    )

@pytest.fixture
def test_ticket(test_show):
    """Create a test ticket."""
    from tickets.models import Ticket

    return Ticket.objects.create(
        show=test_show,
        name="General Admission",
        description="Standard ticket",
        price=Decimal("25.00"),
        quantity=100,
        is_donation_based=False,
    )

@pytest.fixture
def test_promo_code(test_show):
    """Create a test promo code."""
    from tickets.models import TicketPromoCode

    return TicketPromoCode.objects.create(
        code="TEST20",
        discount=Decimal("0.20"),  # 20% off
        show=test_show,
    )

Test Data Factories (Using factory_boy)

import factory
from factory.django import DjangoModelFactory
from decimal import Decimal
from datetime import timedelta
from django.utils import timezone

class ProducerFactory(DjangoModelFactory):
    class Meta:
        model = 'producers.Producer'

    name = factory.Faker('company')

class ProducerFinancialFactory(DjangoModelFactory):
    class Meta:
        model = 'producers.ProducerFinancial'

    producer = factory.SubFactory(ProducerFactory)
    stripe_account_id = factory.Sequence(lambda n: f'acct_test{n}')
    stripe_account_link = factory.LazyAttribute(
        lambda obj: f'https://test.stripe.com/{obj.stripe_account_id}'
    )

class ShowFactory(DjangoModelFactory):
    class Meta:
        model = 'tickets.Show'

    title = factory.Faker('catch_phrase')
    description = factory.Faker('paragraph')
    producer = factory.SubFactory(ProducerFactory)
    published = True

    @factory.lazy_attribute
    def door_time(self):
        return timezone.now() + timedelta(days=7)

    @factory.lazy_attribute
    def start_time(self):
        return self.door_time + timedelta(hours=1)

    @factory.lazy_attribute
    def end_time(self):
        return self.start_time + timedelta(hours=3)

class TicketFactory(DjangoModelFactory):
    class Meta:
        model = 'tickets.Ticket'

    show = factory.SubFactory(ShowFactory)
    name = factory.Faker('word')
    description = factory.Faker('sentence')
    price = factory.Faker('pydecimal', left_digits=2, right_digits=2, positive=True)
    quantity = 100
    is_donation_based = False

CI/CD Integration

Test Execution Strategy

Local Development:

# Run all tests
pytest apps/api/tickets/tests/

# Run specific priority
pytest apps/api/tickets/tests/ -m "p0"

# Run with coverage
pytest apps/api/tickets/tests/ --cov=tickets --cov-report=html

# Run in parallel
pytest apps/api/tickets/tests/ -n auto

Continuous Integration (GitHub Actions):

name: Checkout Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
    - uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-django pytest-cov pytest-xdist

    - name: Run P0 Tests (Critical)
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
        REDIS_URL: redis://localhost:6379/0
      run: |
        pytest apps/api/tickets/tests/ -m "p0" --cov=tickets

    - name: Run P1 Tests (High Priority)
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
        REDIS_URL: redis://localhost:6379/0
      run: |
        pytest apps/api/tickets/tests/ -m "p1" --cov=tickets --cov-append

    - name: Upload coverage
      uses: codecov/codecov-action@v3

Test Markers (pytest.ini)

[pytest]
DJANGO_SETTINGS_MODULE = brktickets.settings
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
    p0: Critical tests that must pass
    p1: High priority tests for edge cases
    p2: Medium priority tests for performance and security
    slow: Tests that take more than 1 second
    integration: Integration tests requiring external services
    unit: Unit tests with no external dependencies

Coverage Requirements

Target Coverage: - Overall: 90% - Core checkout views: 95% - Validation functions: 100% - Models: 85%

Coverage Exclusions: - Migration files - Test files - Settings files - Development-only code


Appendices

Appendix A: Test Template

"""
Template for new checkout tests.

Priority: P0 | P1 | P2
Category: Core | Edge Case | Performance | Security | Resilience
"""
import pytest
from decimal import Decimal
from django.urls import reverse
from rest_framework import status
from unittest.mock import patch, MagicMock

class TestCheckoutFeature:
    """Test suite for [specific feature]."""

    @pytest.mark.p0  # or p1, p2
    def test_specific_scenario(self, test_show, test_ticket):
        """
        Test [specific scenario description].

        Prerequisites:
        - [List any prerequisites]

        Test Steps:
        1. [Step 1]
        2. [Step 2]
        3. [Step 3]

        Expected Outcomes:
        - [Expected outcome 1]
        - [Expected outcome 2]
        """
        # Arrange
        # ... setup code ...

        # Act
        # ... action code ...

        # Assert
        # ... assertions ...

Appendix B: Common Assertions

# Order assertions
assert order.success == True
assert order.total == expected_total
assert order.platform_fees == expected_platform_fees
assert order.payment_processing_fees == expected_processing_fees

# Ticket inventory assertions
ticket.refresh_from_db()
assert ticket.quantity == expected_quantity

# Error response assertions
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data["status"] == "error"
assert "message" in response.data

# Lock assertions (from cache)
lock_key = f"ticket_lock_{ticket.id}"
assert cache.get(lock_key) is None  # Lock released

# Email/task assertions
mock_send_email.assert_called_once()
assert mock_send_email.call_args[0][0]["order_id"] == order.id

Appendix C: Performance Benchmarks

Target Performance Metrics:

Metric Target Current Notes
Checkout response time (p50) < 2s TBD Includes Stripe call
Checkout response time (p95) < 5s TBD
Lock acquisition time (p95) < 100ms TBD
Transaction duration (p95) < 3s TBD Database operations only
Concurrent capacity 100+ users TBD No lock timeouts
Lock contention (p95) < 1s wait TBD Time waiting for lock

Measurement Tools: - Django Debug Toolbar (local) - pytest-benchmark - locust (load testing) - Application Performance Monitoring (APM) in production

Appendix D: Known Issues & Limitations

Current Limitations: 1. Checkout uses GET instead of POST (should be POST for mutations) 2. No webhook handler for Stripe events (relies on redirect callbacks) 3. No promo code usage limits implemented 4. No rate limiting on checkout endpoint 5. No abandoned cart cleanup 6. Tax calculation delegated entirely to Stripe

Security Considerations: 1. Stripe API key must be kept secure 2. Redis should be password-protected in production 3. Database connection should use SSL 4. Customer email addresses are PII - handle according to GDPR

Appendix E: Glossary

Terms: - Order: Complete purchase transaction with customer info - TicketOrder: Line item in an order for specific ticket type - Attendee: Individual person attending (one per ticket) - Promo Code: Discount code for percentage off - Lock: Redis-based distributed lock to prevent race conditions - Session: Stripe Checkout Session for payment processing - Transaction: Database atomic transaction boundary


Document Approval

Role Name Date Signature
Developer
QA Lead
Tech Lead
Product Manager

Next Steps: 1. Review and approve this test plan 2. Create GitHub issues for each test phase 3. Begin Phase 1 implementation (P0 tests) 4. Schedule regular test coverage reviews 5. Integrate into CI/CD pipeline


This document is a living document and should be updated as the checkout system evolves.