QA Testing Plan: Subscriptions
This document covers QA test cases for the Subscriptions business app located at /business/apps/subscriptions. The app supports creating subscription plans with monthly/quarterly/yearly intervals, managing features, linking courses for access gating, Stripe price integration, and subscriber tracking.
Routes Under Test
Section titled “Routes Under Test”| Route | Purpose |
|---|---|
/business/apps/subscriptions | Dashboard with stats and all plans list |
/business/apps/subscriptions/new | Create plan form (superforms + createPlanSchema) |
/business/apps/subscriptions/[id] | Plan detail with publish, delete, course management, and subscribers list |
Form Actions Reference
Section titled “Form Actions Reference”| Route | Action | Method | Key Fields |
|---|---|---|---|
/business/apps/subscriptions/new | default | POST | title, description, price, interval, features, stripe_price_id |
/business/apps/subscriptions/[id] | publish | POST | (none) |
/business/apps/subscriptions/[id] | delete | POST | (none) |
/business/apps/subscriptions/[id] | add-course | POST | course_id |
/business/apps/subscriptions/[id] | remove-course | POST | course_id |
1. Dashboard and Plan List
Section titled “1. Dashboard and Plan List”| Test ID | Description | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| SUB-001 | Dashboard loads with instructor stats | User is logged in with plans | 1. Navigate to /business/apps/subscriptions | Stats cards display: Total Plans, Published, Subscribers, Revenue. Plan list shows all plans. | P0 |
| SUB-002 | Dashboard shows empty state for new user | User is logged in with no plans | 1. Navigate to /business/apps/subscriptions | Stats section is hidden. Empty state “No plans yet” is displayed. | P1 |
| SUB-003 | Unauthenticated user sees null stats | User is not logged in | 1. Navigate to /business/apps/subscriptions | Page loads with stats: null and empty plans. No crash. | P1 |
| SUB-004 | ”New Plan” button navigates correctly | User is logged in | 1. Click “New Plan” button | Navigates to /business/apps/subscriptions/new. | P0 |
| SUB-005 | Plan links navigate to detail page | User has plans | 1. Click a plan in the list | Navigates to /business/apps/subscriptions/{id}. | P1 |
| SUB-006 | Plan interval displays correct abbreviation | Plans with different intervals exist | 1. View plan list | Monthly shows “/mo”, quarterly shows “/qtr”, yearly shows “/yr”. | P2 |
| SUB-007 | Plan status badges render correctly | Plans with draft, published, inactive statuses exist | 1. View plan list | draft = secondary, published = default, inactive = destructive. | P2 |
2. Create Plan
Section titled “2. Create Plan”| Test ID | Description | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| SUB-010 | Create plan with required fields | User is logged in | 1. Navigate to /business/apps/subscriptions/new 2. Fill title 3. Submit | Plan is created. User is redirected to /business/apps/subscriptions/{new_id}. | P0 |
| SUB-011 | Create plan with all fields | User is logged in | 1. Fill title, description, price, interval, features, stripe_price_id 2. Submit | Plan is created with all fields persisted. | P0 |
| SUB-012 | Validation: empty title rejected | User is logged in | 1. Leave title blank 2. Submit | Form returns 400 with error “Title is required”. | P0 |
| SUB-013 | Validation: title exceeds 255 characters | User is logged in | 1. Enter title with 256+ characters 2. Submit | Form returns 400 with max length error. | P1 |
| SUB-014 | Validation: negative price rejected | User is logged in | 1. Enter price as -5 2. Submit | Form returns 400 with error “Price must be positive”. | P1 |
| SUB-015 | Interval: monthly | User is logged in | 1. Select interval “monthly” 2. Submit | Plan is created with interval: "monthly". | P1 |
| SUB-016 | Interval: quarterly | User is logged in | 1. Select interval “quarterly” 2. Submit | Plan is created with interval: "quarterly". | P1 |
| SUB-017 | Interval: yearly | User is logged in | 1. Select interval “yearly” 2. Submit | Plan is created with interval: "yearly". | P1 |
| SUB-018 | Features array persists correctly | User is logged in | 1. Provide a features array 2. Submit | Plan is created with features stored. | P1 |
| SUB-019 | Stripe price ID persists correctly | User is logged in | 1. Provide stripe_price_id 2. Submit | Plan is created with Stripe price ID linked. | P1 |
| SUB-020 | Unauthenticated user redirected from create page | User is not logged in | 1. Navigate to /business/apps/subscriptions/new | User is redirected to /login. | P0 |
3. Plan Detail and Lifecycle
Section titled “3. Plan Detail and Lifecycle”| Test ID | Description | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| SUB-030 | Load plan detail page | User owns the plan | 1. Navigate to /business/apps/subscriptions/{id} | Plan data loads with linked courses, subscribers list, and instructor courses for selection. | P0 |
| SUB-031 | 404 for non-existent plan | User is logged in | 1. Navigate to /business/apps/subscriptions/{invalid_id} | 404 error “Plan not found” is returned. | P0 |
| SUB-032 | 403 for plan owned by another instructor | User does not own the plan | 1. Navigate to /business/apps/subscriptions/{other_instructor_plan} | 403 Forbidden error is returned. | P0 |
| SUB-033 | Publish a draft plan | User owns a draft plan | 1. Submit the publish action | Returns { published: true }. Plan status updates to “published”. | P0 |
| SUB-034 | Delete a plan | User owns a plan | 1. Submit the delete action | Plan is deleted. User is redirected to /business/apps/subscriptions. | P0 |
| SUB-035 | Subscribers list displays on detail page | User owns a plan with subscribers | 1. Load the plan detail page | Subscribers list is populated with subscriber data. | P1 |
4. Course Access Gating
Section titled “4. Course Access Gating”| Test ID | Description | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| SUB-040 | Add course to plan | User owns the plan and has courses | 1. Submit add-course with course_id | Course is linked to the plan. Returns { courseAdded: true }. | P0 |
| SUB-041 | Add course fails without course_id | User is logged in | 1. Submit add-course without course_id | Returns 400 “Missing course ID”. | P1 |
| SUB-042 | Add course handles server error | User is logged in, service throws | 1. Trigger server error | Returns 500 “Failed to add course”. | P1 |
| SUB-043 | Remove course from plan | User owns the plan with linked courses | 1. Submit remove-course with course_id | Course is unlinked from the plan. Returns { courseRemoved: true }. | P0 |
| SUB-044 | Remove course fails without course_id | User is logged in | 1. Submit remove-course without course_id | Returns 400 “Missing course ID”. | P1 |
| SUB-045 | Remove course handles server error | User is logged in, service throws | 1. Trigger server error | Returns 500 “Failed to remove course”. | P1 |
| SUB-046 | Instructor courses loaded for selection | User owns the plan and has instructor courses | 1. Load plan detail page | instructorCourses array is populated, enabling course selection dropdown. | P1 |
5. Stripe Integration
Section titled “5. Stripe Integration”| Test ID | Description | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| SUB-050 | Plan with stripe_price_id links to Stripe | Plan has stripe_price_id set | 1. Create or view plan with Stripe price ID | Stripe price ID is stored and available for checkout integration. | P0 |
| SUB-051 | Plan without stripe_price_id still functions | Plan has no stripe_price_id | 1. Create plan without Stripe price ID | Plan is created successfully. Price ID field is null/undefined. | P1 |