docs: SSP Essentials manual E2E integration testing guide - #217
docs: SSP Essentials manual E2E integration testing guide#217iloveagent57 wants to merge 1 commit into
Conversation
Covers Stripe sandbox and Braze dev workspace testing for the SSP checkout and email notification flow, including happy path walkthrough, test clock usage for time-based events, and a task/event/settings reference table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
35a0a22 to
07aa99e
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new internal guide for manually validating the SSP Essentials checkout + email notification flow end-to-end against Stripe test mode and the Braze dev workspace, focusing on replaying Stripe events locally and verifying the resulting Celery/Braze behavior.
Changes:
- Introduces a new manual E2E integration testing doc for SSP Essentials billing emails.
- Documents prerequisites (local seed data, Stripe/Braze keys, Celery execution mode) and a payment-receipt happy path.
- Adds guidance for non-Stripe-triggered signup confirmation emails, Stripe test clocks, and a task/event/settings reference.
|
|
||
| ## Prerequisites | ||
|
|
||
| **Devstack.** The app server must be running. Email tasks are async Celery tasks; by default you also need the worker running (`make dev.celery` or equivalent). If running a separate worker container is inconvenient, add `CELERY_ALWAYS_EAGER = True` to `settings/private.py` to execute tasks synchronously inline instead. |
|
|
||
| **Local DB seed.** You need at least one active `SspProduct` record pointing at a valid Stripe price lookup key, and a test user in the LMS with an enterprise admin role for a test enterprise. `get_enterprise_admins` fetches LMS enterprise data to build Braze recipients; if it returns empty, the task fails before reaching Braze. | ||
|
|
||
| **`settings/private.py`.** Add the following (create the file if it doesn't exist -- it's gitignored): |
There was a problem hiding this comment.
nit: People can probably figure it out, but it doesn't hurt to be explicit.
| STRIPE_API_KEY = 'sk_test_...' | ||
|
|
||
| # Run Celery tasks inline without a separate worker (optional but convenient locally) | ||
| CELERY_ALWAYS_EAGER = True |
|
|
||
| **Devstack.** The app server must be running. Email tasks are async Celery tasks; by default you also need the worker running (`make dev.celery` or equivalent). If running a separate worker container is inconvenient, add `CELERY_ALWAYS_EAGER = True` to `settings/private.py` to execute tasks synchronously inline instead. | ||
|
|
||
| **Local DB seed.** You need at least one active `SspProduct` record pointing at a valid Stripe price lookup key, and a test user in the LMS with an enterprise admin role for a test enterprise. `get_enterprise_admins` fetches LMS enterprise data to build Braze recipients; if it returns empty, the task fails before reaching Braze. |
|
|
||
| **2. Complete checkout in Stripe.** Open the checkout URL from the response in a browser. Use test card `4242 4242 4242 4242`, any future expiry, any CVC. Stripe creates a subscription and immediately generates a paid invoice for $0 (trial). | ||
|
|
||
| **3. Note the current timestamp.** Grab a Unix timestamp from a minute before you started (e.g. `date -v-2M +%s` on Mac). This is your `--since` value to avoid pulling unrelated older events. |
There was a problem hiding this comment.
❌ Confirmed, see fetch_and_handle_stripe_events.py#L49
| python manage.py fetch_and_handle_stripe_events \ | ||
| --event-types invoice.created invoice.paid \ | ||
| --since <timestamp> |
There was a problem hiding this comment.
❌ Confirmed, see fetch_and_handle_stripe_events.py#L38
|
|
||
| **Stale price cache.** If you add or change products in the sandbox, `get_all_stripe_prices` returns cached data until TTL expires. Clear it from a Django shell: `from edx_django_utils.cache import TieredCache; TieredCache.dangerous_clear_all_tiers()`. | ||
|
|
||
| **`invoice.paid` before `invoice.created`.** `send_payment_receipt_email` requires a `StripeEventSummary` record created by the `invoice.created` handler. Include both event types in `--event-types` when replaying. |
|
|
||
| ## Common failure modes | ||
|
|
||
| **Worker not running.** Tasks queue silently; nothing sends, no error in the management command output. Start the worker or set `CELERY_ALWAYS_EAGER = True` in `private.py`. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #217 +/- ##
=======================================
Coverage 86.58% 86.58%
=======================================
Files 155 155
Lines 12953 12953
Branches 1239 1239
=======================================
Hits 11215 11215
Misses 1423 1423
Partials 315 315 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| **Devstack.** The app server must be running. Email tasks are async Celery tasks; by default you also need the worker running (`make dev.celery` or equivalent). If running a separate worker container is inconvenient, add `CELERY_ALWAYS_EAGER = True` to `settings/private.py` to execute tasks synchronously inline instead. | ||
|
|
||
| **Local DB seed.** You need at least one active `SspProduct` record pointing at a valid Stripe price lookup key, and a test user in the LMS with an enterprise admin role for a test enterprise. `get_enterprise_admins` fetches LMS enterprise data to build Braze recipients; if it returns empty, the task fails before reaching Braze. | ||
|
|
||
| **`settings/private.py`.** Add the following (create the file if it doesn't exist -- it's gitignored): |
| # Stripe sandbox -- test mode secret key | ||
| # Developers -> API keys in the sandbox dashboard: | ||
| # https://dashboard.stripe.com/acct_1RtEfJQ60jNALKNU/test/dashboard | ||
| STRIPE_API_KEY = 'sk_test_...' |
There was a problem hiding this comment.
On the one hand, we know our exact sandbox, so the url here helps save a step in finding it. On the other hand, enterprise-access is a publicly-viewable repository so we would be exposing an internal stripe id to potential attackers. Will leave it to your judgment which way to go.
|
|
||
| **2. Complete checkout in Stripe.** Open the checkout URL from the response in a browser. Use test card `4242 4242 4242 4242`, any future expiry, any CVC. Stripe creates a subscription and immediately generates a paid invoice for $0 (trial). | ||
|
|
||
| **3. Note the current timestamp.** Grab a Unix timestamp from a minute before you started (e.g. `date -v-2M +%s` on Mac). This is your `--since` value to avoid pulling unrelated older events. |
There was a problem hiding this comment.
date -v-2M +%s is BSD date (macOS only). Linux equivalent is date -d '2 minutes ago' +%s. Since --created-since-hours-ago removes the need for a timestamp entirely, the simplest fix is to drop the date example and use that flag instead.
| | Task | Triggering Stripe event | `private.py` settings key | | ||
| |---|---|---| |
There was a problem hiding this comment.
nit: Again people can probably figure this out but doesn't hurt to be explicit
marlonkeating
left a comment
There was a problem hiding this comment.
Reviewed with Claude's help
|
|
||
| ## Signup confirmation email | ||
|
|
||
| `send_enterprise_provision_signup_confirmation_email` fires at the end of the provisioning workflow (`provisioning/models.py`), not via a Stripe event. To test it, either trigger the full provisioning workflow or call the task directly from a Django shell: |
There was a problem hiding this comment.
nit: It would be nice to document triggering the full provisioning workflow, especially locally if there's a simple-ish way to do that.
Summary
docs/customer-billing/ssp-essentials-integration-testing.mdfor contractor devs testing the SSP checkout and Braze email flowsettings/private.py,CELERY_ALWAYS_EAGER, DB seed), happy path walkthrough (invoice.paid->send_payment_receipt_email), signup confirmation email (provisioning-triggered, not Stripe), Stripe test clock usage for time-based events, and a task/event/settings reference tableSspProduct(not yet implemented) and directs readers to ask for essentials campaign UUIDs once createdTest plan
🤖 Generated with Claude Code