Add E2E test infrastructure and purchase flow tests#10781
Merged
psincraian merged 11 commits intomainfrom Apr 7, 2026
Merged
Conversation
Introduce an in-process E2E testing framework for backend purchase flows. The infrastructure mounts the FastAPI app via httpx ASGI transport, drains Dramatiq background tasks inline via a TaskDrain utility, and mocks external services (Stripe, email, tax, PostHog, Loops, webhooks). Key components: - TaskDrain: reads Dramatiq messages from Redis, executes task functions inline with proper JobQueueManager context for sub-task chaining - StripeSimulator: configures mock return values and generates matching webhook charge objects with consistent IDs - EmailCapture: intercepts emails for assertion - External HTTP guardrail: blocks unmocked outbound calls Includes a working one-time purchase E2E test covering: checkout creation → payment confirmation → Stripe webhook → order verification → email delivery Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorganize tests/e2e/ into a phase-based folder structure: - infra/ — test infrastructure (TaskDrain, StripeSimulator, mocks) - purchase/ — buying something (one_time/, subscription/) - post_purchase/ — actions after order exists (seat claims) - lifecycle/ — ongoing subscription events (renewal) Add new purchase tests: - one_time/test_free_product.py — free product, no Stripe payment - one_time/test_with_discount.py — 20% discount applied to order - subscription/test_happy_path.py — monthly plan → subscription active Infrastructure improvements: - Auto-discover task modules via rglob (no more hardcoded imports) - Coroutine validation after __wrapped__ unwrap - Canary assertion for CurrentMessage._MESSAGE private API - E2E_AUTH preset to avoid copy-pasting 5-scope auth decorator - Autouse _link_user_to_org fixture (reduces test params) - HTTP guardrail blocking unmocked external calls - TaskDrainError chains to root cause exception - Safe Stripe mock defaults (customer, payment method) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename test files to be descriptive: - test_happy_path.py → test_fixed_price.py (both one_time and subscription) Implement post_purchase/test_seat_claim.py: - Buy 3 seats on a seat-based product - Assign a seat to a teammate via API - Retrieve invitation token from DB - Claim seat with token → status=claimed, benefits enqueued Implement lifecycle/test_renewal.py: - Create active subscription with period ending now - Call subscription_service.cycle() directly - Drain order.create_subscription_order task - Verify new order with billing_reason=subscription_cycle Add order.invoice to DEFAULT_IGNORED_ACTORS (requires S3/MinIO). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Import BUYER_EMAIL/NAME/BILLING_ADDRESS from purchase/conftest instead of redefining in test_seat_claim.py - Move E2E_SEAT_AUTH to post_purchase/conftest.py - Remove E2E_LIFECYCLE_AUTH, use E2E_AUTH directly (superset) - Import monthly_product fixture from subscription/conftest into lifecycle/conftest instead of redefining - Move UserOrganization import to top-level in test_seat_claim.py - Remove unnecessary sorted() wrapping rglob in task discovery Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of calling subscription_service.cycle() directly, the test now enqueues the subscription.cycle task through the drain pipeline — the same way APScheduler triggers it in production. Add trigger_subscription_cycle() helper in lifecycle/conftest.py that enqueues via JobQueueManager and returns the drain result, consistent with how simulate_stripe_webhook works for purchase tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New test: post_purchase/test_benefit_grants.py (Option C pattern) - test_custom_benefit_granted: no-op strategy, grant record created - test_license_key_generated: key ID + display key stored on grant - test_meter_credit_applied: 100 units credited to meter - test_feature_flag_granted: presence-based grant exists Shared _purchase_product() helper eliminates purchase flow duplication across benefit type tests. Each test method has clear Given/When/Then. Updated all existing tests with Given/When/Then comments, removed redundant section separators and narrating comments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
E2E tests should only assert on client-visible outcomes: HTTP status, database state (via API), and emails sent. Internal details like which Dramatiq tasks ran are implementation details that couple tests to the task graph structure. Removed all `assert "actor.name" in executed` patterns. The drain() is still called (it raises TaskDrainError on failure) but its result is no longer inspected for specific task names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…a fixes 1. Extract complete_purchase() helper + CompletedPurchase dataclass into purchase/conftest.py. Refactored all 8 tests that duplicated the checkout-confirm-webhook sequence to use it instead. 2. Add test_payment_failure.py — charge.failed webhook results in no order created and checkout returns to open status. 3. Add test_with_tax.py — 10% exclusive tax on $50 product verifies tax_amount appears on the order (was hardcoded to 0 in all tests). 4. Fix task_drain.py infrastructure: - rglob discovery now excludes tests/, __pycache__, hidden dirs - Import errors logged as warnings instead of silently swallowed - __wrapped__ unwrapping loops with depth guard (MAX_UNWRAP_DEPTH=10) - RuntimeError catch narrowed to "not initialized" message only - DrainFn is now a Protocol with full parameter signatures 5. DrainFn Protocol preserves ignored_actors and raise_on_failure parameter names for IDE autocomplete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
psincraian
commented
Apr 3, 2026
Comment on lines
+38
to
+40
| "order.balance", | ||
| # Uploads invoice PDF to S3/MinIO | ||
| "order.invoice", |
Contributor
Author
There was a problem hiding this comment.
I'm not sure if we need to test those 2. I would love to do it but looks a little bit more complicated
The renewal test now creates the subscription via complete_purchase() instead of create_subscription() fixture, ensuring the initial state matches what a real purchase produces. The test verifies both the original subscription_create order and the renewal subscription_cycle order exist. Also adds stripe_service mock to order.service (for renewal payment trigger) and default create_payment_intent return value. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TaskDrainutility that reads Dramatiq messages from Redis and executes task functions inlinepurchase/,post_purchase/,lifecycle/Infrastructure (
tests/e2e/infra/)JobQueueManagerto Redis, drains all queues by executing task functions with properCurrentMessagecontext. Auto-discovers task modules viarglob. RaisesTaskDrainErrorwith chained exception on failure.expect_payment()/send_charge_webhook()API.Tests
test_fixed_pricetest_free_producttest_with_discounttest_with_taxtest_payment_failuretest_fixed_pricetest_seat_claimtest_benefit_grantstest_renewalTest plan
Generated with Claude Code