# Subscription/Recurly Test Plan (Rails 8 Branch) ## Objective Close subscription lifecycle regressions by adding executable tests in `web` and `ruby` that do not depend on live Recurly network calls. ## What Exists - Webhook coverage exists (request + model) and is active. - Legacy Recurly suites were revived and are now active: - `ruby/spec/jam_ruby/recurly_client_spec.rb` - `ruby/spec/jam_ruby/models/user_subscriptions_spec.rb` - Active live Recurly integration coverage now exists in (opt-out via `SKIP_LIVE_RECURLY=1`): - `ruby/spec/jam_ruby/integration/recurly_live_integration_spec.rb` ## Priority 1: API Subscription Lifecycle (`web/spec/requests`) Add request specs for `ApiRecurlyController` with `RecurlyClient` stubbed. ### 1.1 Plan-first then payment - `POST /api/recurly/change_subscription` with paid plan and no billing info: - expect desired plan is set and response indicates payment method still needed. - `POST /api/recurly/update_payment` with token: - expect `handle_create_subscription` called with user’s desired plan. - expect success payload includes plan metadata and `has_billing_info = true`. ### 1.2 Payment-first then plan - `POST /api/recurly/update_payment` first (no desired plan yet): - expect no failure and billing info persisted. - Then `POST /api/recurly/change_subscription` to paid plan: - expect desired plan update call and success response. ### 1.3 Negative paths - `change_subscription` with unchanged plan: - expect 422 and `No change made to plan`. - `update_payment` when client raises `RecurlyClientError`: - expect 404 + serialized error payload. ## Priority 2: Sync Decision Tree (`ruby/spec/jam_ruby/models`) Add focused unit specs for `RecurlyClient#sync_subscription` with Recurly API mocked. ### 2.1 Unchanged/good-standing path - account exists, not past_due, active subscription, desired == effective. - expect sync code `good_standing_unchanged` and no plan mutation. ### 2.2 Canceled/expired path - no active subscription (or expired). - expect effective plan set to free (`nil`) and sync code `no_subscription_or_expired`. ### 2.3 Past-due path - account `has_past_due_invoice = true`. - expect effective plan dropped to free and sync code `is_past_due_changed`. ### 2.4 Trial and free-month behavior - user in trial with desired gold and active account. - verify trial code path + post-trial behavior once time advances (use `Timecop` or `travel_to` depending what is stable in this suite). ## Priority 3: Hourly Job Integration (`ruby/spec/jam_ruby/resque` or model-level) - Validate `JamRuby::HourlyJob.perform` triggers `User.hourly_check`. - Validate `User.subscription_sync` only selects intended users and calls client sync. - Validate `User.subscription_transaction_sync` advances `GenericState.recurly_transactions_last_sync_at` using mocked transaction stream. ## Priority 4: Browser Coverage (`web/spec/features`) Add high-value feature coverage for subscription management screen: - plan-first flow shows payment-needed state and routes to update payment. - payment-first flow then selecting plan results in active paid subscription state. Use stubs/fakes around `RecurlyClient` in feature env to avoid external dependency. ## Execution Order 1. Request specs for `ApiRecurlyController` (fast, high signal). 2. `RecurlyClient#sync_subscription` specs (logic-heavy, deterministic). 3. Hourly sync integration coverage. 4. Browser feature tests for UX flow parity. ## Definition of Done - New tests are active (not `xdescribe`/commented out). - Both plan-first and payment-first lifecycles are covered. - Hourly sync scenarios for unchanged, canceled/expired, and past-due are covered. - First-free-month/gold behavior has explicit time-based assertions. - `docs/dev/subscriptions.md` updated with links to new spec files and scenarios.