# CEPI LMS — QA

> Verified against codebase. Last updated: 2026-06-02.

---

## 1. QA Strategy

- **Feature tests** (Laravel HTTP tests) for all critical flows
- **Unit tests** for pure service logic (BunnyVideoService, DeviceBindingService)
- **Manual UI verification** against `html/design1/` and `html/design2/` prototypes
- **Role-based regression** after any auth/middleware change
- **Payment flow testing** before enabling any gateway in production
- **API contract testing** (Postman / PHPUnit HTTP) once mobile API is built

---

## 2. Current Test Coverage

- `tests/` directory exists with `TestCase.php` base class
- No feature or unit test files were found beyond the base class
- **All test coverage is manual at this point**
- `php artisan test` command is configured in `composer.json`

---

## 3. Critical Regression Paths

### Auth & Device Binding
- [ ] Register with email → verify with 6-digit code → login
- [ ] Login with Google OAuth
- [ ] Login on new browser → device binding notice shown
- [ ] Login on new browser within 24h cooldown → blocked with error message
- [ ] Admin resets device → user can bind new browser
- [ ] Invalid verification code → appropriate error shown
- [ ] Expired verification code → user prompted to resend

### Enrollment & Course Access
- [ ] Free course → auto-enrolled on checkout → accessible immediately
- [ ] Paid course → cannot access `/learn/*` without enrollment
- [ ] Bank transfer submitted → payment status `pending` → course not accessible yet
- [ ] Admin approves payment → enrollment created → course accessible
- [ ] Cancelled enrollment → `/learn/*` returns 403
- [ ] Expired enrollment (`expires_at` in past) → 403

### Lesson Player
- [ ] Enrolled student can access course hub (`/learn/{slug}`)
- [ ] Enrolled student can open a lesson (`/learn/{slug}/lessons/{id}`)
- [ ] Lesson from a different course returns 404 (cross-course URL attack)
- [ ] Bunny embed URL is generated with expiry token
- [ ] Preview lesson (`is_preview = true`) visible without enrollment (if applicable)
- [ ] Non-enrolled user redirected to enrollment page

### Lesson Progress
- [ ] AJAX progress update returns `{ status: 'success', is_completed: ... }`
- [ ] `watched_seconds` never decreases (monotonic max)
- [ ] Lesson marked complete when `watched_seconds >= duration * 0.9`
- [ ] `progress_percent` on enrollment updates after each progress save
- [ ] Marking complete then incomplete resets `completed_at`
- [ ] `watch_count` increments on each `started: true` signal

### Instructor Dashboard
- [ ] Instructor can only see and edit own courses
- [ ] Instructor can create module/lesson via AJAX
- [ ] Module and lesson ordering persists after drag/reorder
- [ ] Submit for review changes course status to `pending_review`
- [ ] Instructor cannot access `/admin/*` routes

### Admin Flows
- [ ] Admin can create/edit/publish/archive any course
- [ ] Admin can manually enroll any user in any course
- [ ] Admin can view and update payment status
- [ ] Admin can approve bank transfer → enrollment created
- [ ] Admin can reset user devices
- [ ] Admin can manage categories, blog, media, settings
- [ ] Admin settings: bank account details appear on manual checkout page

### Payment Flows
- [ ] Manual checkout page shows bank details from settings
- [ ] Submitting transfer reference creates `pending` payment
- [ ] Re-submitting when pending payment exists reuses same record
- [ ] Already enrolled user sees "already active" message
- [ ] Free course checkout redirects to dashboard with success message

### Public Pages
- [ ] Course catalog shows only `published` courses
- [ ] Course detail shows modules/lessons without player (no enrollment)
- [ ] Blog listing and post pages render correctly
- [ ] Contact form submits and stores `contact_messages` record
- [ ] Sitemap returns valid XML
- [ ] 404 on unpublished course slug

---

## 4. Automated Test Recommendations

### Unit Tests (Services)

```php
// BunnyVideoServiceTest
test('generates signed embed URL with expiry token')
test('generated URL contains videoId')
test('throws exception when token key not configured')
test('extractVideoId returns UUID from raw string')
test('extractVideoId returns UUID from full URL')
test('extractVideoId returns null for invalid input')

// DeviceBindingServiceTest
test('returns existing device when trusted token matches')
test('throws ValidationException when new browser within cooldown')
test('creates new device when cooldown expired')
test('blocks old device when new device is bound')
test('resetDevices blocks all active devices for user')
```

### Feature Tests (HTTP)

```php
// EnrollmentTest
test('student cannot access lesson without enrollment')
test('student can access lesson with active enrollment')
test('student is blocked from expired enrollment')
test('cross-course lesson attack returns 404')

// ProgressTest
test('progress update returns json success')
test('watched_seconds does not decrease')
test('lesson completed when 90 percent watched')
test('enrollment progress_percent updates after lesson completion')

// CheckoutTest
test('free course enrolls immediately')
test('bank transfer creates pending payment')
test('duplicate bank transfer reuses pending payment')
test('already enrolled student sees already active message')

// AuthTest
test('login blocked on new device within cooldown')
test('login creates new device on first login')
test('admin can reset user devices')
```

---

## 5. Role-Based Test Matrix

| Action | Guest | Student | Instructor | Admin |
|---|---|---|---|---|
| View course catalog | ✅ | ✅ | ✅ | ✅ |
| View course detail | ✅ | ✅ | ✅ | ✅ |
| Access lesson player | ❌ | ✅ (enrolled) | ✅ (enrolled) | ✅ |
| Submit checkout | ❌ | ✅ | ✅ | ✅ |
| Access admin panel | ❌ | ❌ | ❌ | ✅ |
| Access instructor panel | ❌ | ❌ | ✅ (own) | ✅ |
| Edit another instructor's course | ❌ | ❌ | ❌ | ✅ |
| Reset user device | ❌ | ❌ | ❌ | ✅ |
| Approve payment | ❌ | ❌ | ❌ | ✅ |
| Manage blog | ❌ | ❌ | ❌ | ✅ |

---

## 6. Security Test Cases

- [ ] Direct URL access to `/admin/*` without admin role → 403/redirect
- [ ] Direct URL access to `/instructor/*` without instructor role → 403/redirect
- [ ] Access `/learn/{course}/lessons/{lesson}` with lesson from a different course → 404
- [ ] POST to `/learn/lessons/{lesson}/progress` for a non-enrolled lesson → 403
- [ ] POST to `/webhooks/payments/stripe` with invalid payload → handled gracefully
- [ ] Register with existing email → validation error
- [ ] Submit bank transfer twice for same course → reuses pending payment (no duplicate)
- [ ] Attempt to enumerate Bunny video IDs directly — signed URL required

---

## 7. Mobile API Test Cases (Future)

Once API is built:
- [ ] Login returns Sanctum token
- [ ] Unauthenticated API request returns 401
- [ ] Get course list with pagination
- [ ] Get lesson detail returns `bunny_embed_url` (signed, short-lived)
- [ ] Post lesson progress with `watched_seconds`
- [ ] Token refresh flow
- [ ] Logout invalidates token

---

## 8. Production Readiness Checklist

- [ ] All routes return appropriate status codes (no silent failures)
- [ ] `APP_DEBUG=false` in production
- [ ] Error pages (404, 403, 500) are user-friendly and not exposing stack traces
- [ ] All sensitive keys in `.env` (verified: Bunny, payment gateways)
- [ ] `php artisan test` passes
- [ ] `npm run build` produces clean assets
- [ ] Database migrations run cleanly on fresh schema
- [ ] Queue worker running for any queued jobs
- [ ] Session + cache configured for production (not `file` driver)
- [ ] JazzCash hash verification implemented before enabling
- [ ] Stripe webhook signature verified before enabling
- [ ] Bunny CDN Referer lock configured

---

## 9. Build and Test Commands

```bash
# Run all tests
php artisan test

# Run with coverage
php artisan test --coverage

# Build frontend assets
npm run build

# Check routes
php artisan route:list

# Check migration status
php artisan migrate:status

# Clear all caches
php artisan optimize:clear
```
