removed build files
This commit is contained in:
parent
130421e971
commit
7c6962909e
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
cmd.exe
|
||||||
bin
|
bin
|
||||||
coverage.out
|
coverage.out
|
||||||
coverage
|
coverage
|
||||||
|
|
|
||||||
514
docs/TEST_PLAN.md
Normal file
514
docs/TEST_PLAN.md
Normal file
|
|
@ -0,0 +1,514 @@
|
||||||
|
# Yimaru LMS Backend — Test Plan
|
||||||
|
|
||||||
|
## 1. System Overview
|
||||||
|
|
||||||
|
| Component | Tech |
|
||||||
|
|-----------|------|
|
||||||
|
| Language | Go 1.x |
|
||||||
|
| Framework | Fiber v2 |
|
||||||
|
| Database | PostgreSQL (pgx/v5, sqlc) |
|
||||||
|
| Auth | JWT (access + refresh tokens) |
|
||||||
|
| Video | Vimeo API, CloudConvert |
|
||||||
|
| Payments | ArifPay |
|
||||||
|
| Notifications | WebSocket, SMS (AfroSMS), Resend, Push |
|
||||||
|
| Logging | zap (MongoDB), slog |
|
||||||
|
|
||||||
|
**Roles:** `SUPER_ADMIN`, `ADMIN`, `STUDENT`, `INSTRUCTOR`, `SUPPORT`
|
||||||
|
**Team Roles:** `super_admin`, `admin`, `content_manager`, `support_agent`, `instructor`, `finance`, `hr`, `analyst`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Test Strategy
|
||||||
|
|
||||||
|
### Test Pyramid
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐
|
||||||
|
│ E2E / │ ← API integration tests (Postman/httptest)
|
||||||
|
│ API │
|
||||||
|
┌┴─────────┴┐
|
||||||
|
│ Service │ ← Business logic unit tests (mocked stores)
|
||||||
|
┌┴────────────┴┐
|
||||||
|
│ Repository │ ← DB integration tests (test DB + sqlc)
|
||||||
|
┌┴──────────────┴┐
|
||||||
|
│ Domain/Utils │ ← Pure unit tests (no deps)
|
||||||
|
└────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority Levels
|
||||||
|
- **P0 — Critical:** Auth, Payments, Subscriptions — money + access
|
||||||
|
- **P1 — High:** Course CRUD, Video upload pipeline, Ratings
|
||||||
|
- **P2 — Medium:** Notifications, Questions, Issue Reporting, Team Mgmt
|
||||||
|
- **P3 — Low:** Activity Logs, Analytics, Settings, Static files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Module Test Plans
|
||||||
|
|
||||||
|
### 3.1 Authentication & Authorization (P0)
|
||||||
|
|
||||||
|
#### Unit Tests
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| AUTH-01 | Register user with valid data | valid phone, email, password | 201, user created with status=PENDING |
|
||||||
|
| AUTH-02 | Register with existing email | duplicate email | 400/409, `ErrEmailAlreadyRegistered` |
|
||||||
|
| AUTH-03 | Register with existing phone | duplicate phone | 400/409, `ErrPhoneAlreadyRegistered` |
|
||||||
|
| AUTH-04 | Login with correct credentials | valid email+password | 200, access_token + refresh_token |
|
||||||
|
| AUTH-05 | Login with wrong password | valid email, bad password | 401 |
|
||||||
|
| AUTH-06 | Login unverified user | PENDING user | 401, `ErrUserNotVerified` |
|
||||||
|
| AUTH-07 | Refresh token (valid) | valid refresh token | 200, new access_token |
|
||||||
|
| AUTH-08 | Refresh token (expired/revoked) | expired/revoked token | 401 |
|
||||||
|
| AUTH-09 | Google OAuth login (Android) | valid Google ID token | 200, tokens returned |
|
||||||
|
| AUTH-10 | Logout | valid token | 200, refresh token revoked |
|
||||||
|
| AUTH-11 | OTP send | valid phone/email | 200, OTP generated |
|
||||||
|
| AUTH-12 | OTP verify (correct) | correct OTP | 200, user status → ACTIVE |
|
||||||
|
| AUTH-13 | OTP verify (wrong/expired) | wrong OTP | 400 |
|
||||||
|
| AUTH-14 | OTP resend | valid user | 200, new OTP |
|
||||||
|
| AUTH-15 | Password reset flow | sendResetCode → verify → resetPassword | 200 at each step |
|
||||||
|
|
||||||
|
#### Middleware Tests
|
||||||
|
| ID | Test Case | Expected |
|
||||||
|
|----|-----------|----------|
|
||||||
|
| MW-01 | Request without Authorization header | 401 |
|
||||||
|
| MW-02 | Request with malformed JWT | 401 |
|
||||||
|
| MW-03 | Request with expired JWT | 401 |
|
||||||
|
| MW-04 | Valid token → user_id/role in Locals | Next handler receives correct locals |
|
||||||
|
| MW-05 | `SuperAdminOnly` — ADMIN role | 403 |
|
||||||
|
| MW-06 | `SuperAdminOnly` — SUPER_ADMIN role | Next called |
|
||||||
|
| MW-07 | `OnlyAdminAndAbove` — STUDENT role | 403 |
|
||||||
|
| MW-08 | `OnlyAdminAndAbove` — ADMIN role | Next called |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 User Management (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| USR-01 | Get user profile (own) | auth token | 200, profile data |
|
||||||
|
| USR-02 | Update user profile | valid fields | 200, updated |
|
||||||
|
| USR-03 | Upload profile picture (jpg) | ≤5MB jpg | 200, `/static/profile_pictures/<uuid>.webp` (if CloudConvert on) |
|
||||||
|
| USR-04 | Upload profile picture (>5MB) | 6MB file | 400, file too large |
|
||||||
|
| USR-05 | Upload profile picture (invalid type) | .pdf file | 400, invalid file type |
|
||||||
|
| USR-06 | Profile picture CloudConvert fallback | CloudConvert disabled | 200, saved as original format |
|
||||||
|
| USR-07 | Check profile completed | user_id | 200, boolean |
|
||||||
|
| USR-08 | Update knowledge level | valid level | 200 |
|
||||||
|
| USR-09 | Get all users (admin) | admin token | 200, paginated list |
|
||||||
|
| USR-10 | Delete user | admin token, user_id | 200, user deleted |
|
||||||
|
| USR-11 | Search user by name/phone | search term | 200, results |
|
||||||
|
| USR-12 | Check phone/email exist | phone+email | 200, exists flags |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 Admin Management (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| ADM-01 | Create admin (SUPER_ADMIN) | valid admin data | 201 |
|
||||||
|
| ADM-02 | Create admin (ADMIN role) | valid data | 403, SuperAdminOnly |
|
||||||
|
| ADM-03 | Get all admins | SUPER_ADMIN token | 200, list |
|
||||||
|
| ADM-04 | Update admin | SUPER_ADMIN token | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 Course Categories (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| CAT-01 | Create category | `{ "name": "Math" }` | 201, category returned |
|
||||||
|
| CAT-02 | Create category (empty name) | `{ "name": "" }` | 400 |
|
||||||
|
| CAT-03 | Get all categories (paginated) | limit=10, offset=0 | 200, list + total_count |
|
||||||
|
| CAT-04 | Get category by ID | valid ID | 200 |
|
||||||
|
| CAT-05 | Get category by ID (not found) | ID=999999 | 404 |
|
||||||
|
| CAT-06 | Update category | new name | 200 |
|
||||||
|
| CAT-07 | Delete category | valid ID | 200 |
|
||||||
|
| CAT-08 | Delete category (cascade) | category with courses | 200, courses also deleted |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 Courses (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| CRS-01 | Create course | category_id + title | 201, sends notifications to students |
|
||||||
|
| CRS-02 | Create course (invalid category) | nonexistent category_id | 500 (FK violation) |
|
||||||
|
| CRS-03 | Get course by ID | valid ID | 200, with thumbnail |
|
||||||
|
| CRS-04 | Get courses by category (paginated) | category_id, limit, offset | 200 |
|
||||||
|
| CRS-05 | Update course | title, description, thumbnail | 200, activity log recorded |
|
||||||
|
| CRS-06 | Upload course thumbnail (jpg→webp) | valid image | 200, stored as webp if CloudConvert enabled |
|
||||||
|
| CRS-07 | Upload course thumbnail (>10MB) | large file | 400 |
|
||||||
|
| CRS-08 | Delete course | valid ID | 200, cascades to sub-courses |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 Sub-Courses (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| SUB-01 | Create sub-course | course_id, title, level=BEGINNER | 201, notifications sent |
|
||||||
|
| SUB-02 | Create sub-course (invalid level) | level=EXPERT | 500 (CHECK constraint) |
|
||||||
|
| SUB-03 | List sub-courses by course | course_id | 200, list + count |
|
||||||
|
| SUB-04 | List active sub-courses | — | 200, only is_active=true |
|
||||||
|
| SUB-05 | Update sub-course | title, display_order | 200 |
|
||||||
|
| SUB-06 | Upload sub-course thumbnail | valid image | 200 |
|
||||||
|
| SUB-07 | Deactivate sub-course | valid ID | 200, is_active=false |
|
||||||
|
| SUB-08 | Delete sub-course | valid ID | 200, cascades to videos |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.7 Sub-Course Videos (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| VID-01 | Create video (direct URL) | video_url, title | 201, provider=DIRECT |
|
||||||
|
| VID-02 | Upload video file (with Vimeo) | file + Vimeo configured | 201, provider=VIMEO, status=DRAFT |
|
||||||
|
| VID-03 | Upload video (CloudConvert + Vimeo) | file + both configured | 201, video compressed then uploaded |
|
||||||
|
| VID-04 | Upload video (>500MB) | large file | 400 |
|
||||||
|
| VID-05 | Create video via Vimeo pull URL | source_url | 201 |
|
||||||
|
| VID-06 | Import from existing Vimeo ID | vimeo_video_id | 201, thumbnail from Vimeo |
|
||||||
|
| VID-07 | Publish video | video_id | 200, is_published=true |
|
||||||
|
| VID-08 | Get published videos by sub-course | sub_course_id | 200, only published |
|
||||||
|
| VID-09 | Update video metadata | title, description, status | 200 |
|
||||||
|
| VID-10 | Delete video | video_id | 200 |
|
||||||
|
| VID-11 | CloudConvert disabled fallback | upload without CC | 201, no compression step |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.8 Learning Tree (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Expected |
|
||||||
|
|----|-----------|----------|
|
||||||
|
| TREE-01 | Full learning tree (populated) | 200, nested courses → sub-courses |
|
||||||
|
| TREE-02 | Full learning tree (empty) | 200, empty array |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.9 Questions System (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| Q-01 | Create MCQ question | question_text, type=MCQ, options | 201 |
|
||||||
|
| Q-02 | Create TRUE_FALSE question | type=TRUE_FALSE | 201 |
|
||||||
|
| Q-03 | Create SHORT_ANSWER question | type=SHORT_ANSWER, acceptable answers | 201 |
|
||||||
|
| Q-04 | List questions (filtered) | type=MCQ, difficulty=EASY | 200, filtered list |
|
||||||
|
| Q-05 | Search questions | search query | 200, matching results |
|
||||||
|
| Q-06 | Update question with options | new options | 200, old options replaced |
|
||||||
|
| Q-07 | Delete question | question_id | 200, cascades options/answers |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.10 Question Sets (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| QS-01 | Create question set (PRACTICE) | type=PRACTICE, owner | 201 |
|
||||||
|
| QS-02 | Create question set (INITIAL_ASSESSMENT) | type=INITIAL_ASSESSMENT | 201 |
|
||||||
|
| QS-03 | Add question to set | set_id, question_id | 201 |
|
||||||
|
| QS-04 | Add duplicate question to set | same set_id+question_id | 409/error |
|
||||||
|
| QS-05 | Reorder question in set | display_order | 200 |
|
||||||
|
| QS-06 | Remove question from set | set_id, question_id | 200 |
|
||||||
|
| QS-07 | Get questions in set | set_id | 200, ordered list |
|
||||||
|
| QS-08 | Add user persona | set_id, user_id | 200 |
|
||||||
|
| QS-09 | Remove user persona | set_id, user_id | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.11 Subscription Plans (P0)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| PLAN-01 | Create plan (admin) | name, price, duration | 201 |
|
||||||
|
| PLAN-02 | List plans (public, no auth) | — | 200 |
|
||||||
|
| PLAN-03 | Get plan by ID (public) | plan_id | 200 |
|
||||||
|
| PLAN-04 | Update plan | new price | 200 |
|
||||||
|
| PLAN-05 | Delete plan | plan_id | 200 |
|
||||||
|
| PLAN-06 | Duration calculation (MONTH) | start + 3 MONTH | +3 months |
|
||||||
|
| PLAN-07 | Duration calculation (YEAR) | start + 1 YEAR | +1 year |
|
||||||
|
| PLAN-08 | Duration calculation (DAY) | start + 30 DAY | +30 days |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.12 User Subscriptions (P0)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| SUBS-01 | Admin creates subscription (no payment) | user_id, plan_id | 201, status=ACTIVE |
|
||||||
|
| SUBS-02 | User subscribes with payment | plan_id | 201, status=PENDING, payment initiated |
|
||||||
|
| SUBS-03 | Get my subscription | auth token | 200, current active sub |
|
||||||
|
| SUBS-04 | Get subscription history | auth token | 200, all subs |
|
||||||
|
| SUBS-05 | Check subscription status | auth token | 200, active/expired |
|
||||||
|
| SUBS-06 | Cancel subscription | subscription_id | 200, status=CANCELLED |
|
||||||
|
| SUBS-07 | Set auto-renew on | subscription_id, true | 200 |
|
||||||
|
| SUBS-08 | Set auto-renew off | subscription_id, false | 200 |
|
||||||
|
| SUBS-09 | Expired subscription status check | expired sub | status=EXPIRED |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.13 Payments — ArifPay (P0)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| PAY-01 | Initiate payment | plan_id, phone, email | 201, payment URL returned |
|
||||||
|
| PAY-02 | Verify payment (success) | valid session_id | 200, status=SUCCESS, subscription activated |
|
||||||
|
| PAY-03 | Verify payment (failed) | failed session | 200, status=FAILED |
|
||||||
|
| PAY-04 | Get my payments | auth token | 200, payment list |
|
||||||
|
| PAY-05 | Cancel payment | payment_id | 200, status=CANCELLED |
|
||||||
|
| PAY-06 | Webhook handler (valid) | valid ArifPay webhook | 200, payment updated |
|
||||||
|
| PAY-07 | Webhook handler (invalid signature) | tampered data | 400/401 |
|
||||||
|
| PAY-08 | Get payment methods (public) | — | 200, methods list |
|
||||||
|
| PAY-09 | Direct payment (OTP flow) | phone, method | 200, OTP sent |
|
||||||
|
| PAY-10 | Verify direct payment OTP | correct OTP | 200, payment confirmed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.14 Ratings (P1)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| RAT-01 | Rate app (5 stars) | target_type=app, target_id=0, stars=5 | 201 |
|
||||||
|
| RAT-02 | Rate course (3 stars + review) | target_type=course, target_id=1, stars=3, review="good" | 201 |
|
||||||
|
| RAT-03 | Rate sub-course | target_type=sub_course, target_id=1, stars=4 | 201 |
|
||||||
|
| RAT-04 | Update existing rating (upsert) | same user+target, new stars | 200, stars updated |
|
||||||
|
| RAT-05 | Invalid stars (0) | stars=0 | 400 |
|
||||||
|
| RAT-06 | Invalid stars (6) | stars=6 | 400 |
|
||||||
|
| RAT-07 | Invalid target_type | target_type=video | 400 |
|
||||||
|
| RAT-08 | Get my rating for target | target_type=course, target_id=1 | 200, my rating |
|
||||||
|
| RAT-09 | Get my rating (not rated yet) | — | 404 |
|
||||||
|
| RAT-10 | Get ratings by target (paginated) | target_type=course, limit=20 | 200, list |
|
||||||
|
| RAT-11 | Get rating summary | target_type=course, target_id=1 | 200, avg + count |
|
||||||
|
| RAT-12 | Get rating summary (no ratings) | unrated target | 200, avg=0, count=0 |
|
||||||
|
| RAT-13 | Get all my ratings | auth token | 200, list |
|
||||||
|
| RAT-14 | Delete own rating | rating_id | 200 |
|
||||||
|
| RAT-15 | Delete other user's rating | other's rating_id | fails (no rows) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.15 Notifications (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| NOT-01 | WebSocket connection | valid auth | 101 upgrade |
|
||||||
|
| NOT-02 | Get user notifications | auth token | 200, list |
|
||||||
|
| NOT-03 | Mark as read | notification_id | 200 |
|
||||||
|
| NOT-04 | Mark all as read | — | 200 |
|
||||||
|
| NOT-05 | Mark as unread | notification_id | 200 |
|
||||||
|
| NOT-06 | Count unread | — | 200, count |
|
||||||
|
| NOT-07 | Delete user notifications | — | 200 |
|
||||||
|
| NOT-08 | Auto-notify on course creation | create course | students receive in-app notification |
|
||||||
|
| NOT-09 | Auto-notify on sub-course creation | create sub-course | students notified |
|
||||||
|
| NOT-10 | Send SMS (AfroSMS) | phone + message | 200 |
|
||||||
|
| NOT-11 | Register device token (push) | device token | 200 |
|
||||||
|
| NOT-12 | Unregister device token | device token | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.16 Issue Reporting (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| ISS-01 | Create issue | subject, description, type=bug | 201 |
|
||||||
|
| ISS-02 | Get my issues | auth token | 200 |
|
||||||
|
| ISS-03 | Get all issues (admin) | admin token | 200 |
|
||||||
|
| ISS-04 | Update issue status (admin) | status=resolved | 200, notifications sent |
|
||||||
|
| ISS-05 | Delete issue (admin) | issue_id | 200 |
|
||||||
|
| ISS-06 | Get user issues (admin) | user_id | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.17 Team Management (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| TM-01 | Team member login | email + password | 200, tokens |
|
||||||
|
| TM-02 | Create team member (admin) | valid data, role=instructor | 201 |
|
||||||
|
| TM-03 | Create team member (duplicate email) | existing email | 409 |
|
||||||
|
| TM-04 | Get all team members (admin) | admin token | 200, list |
|
||||||
|
| TM-05 | Get team member stats (admin) | admin token | 200 |
|
||||||
|
| TM-06 | Update team member status | status=suspended | 200 |
|
||||||
|
| TM-07 | Delete team member (SUPER_ADMIN only) | super_admin token | 200 |
|
||||||
|
| TM-08 | Delete team member (ADMIN) | admin token | 403 |
|
||||||
|
| TM-09 | Change team member password | old+new password | 200 |
|
||||||
|
| TM-10 | Get my team profile | team auth token | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.18 Vimeo Integration (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| VIM-01 | Get video info | vimeo_video_id | 200, video details |
|
||||||
|
| VIM-02 | Get embed code | vimeo_video_id | 200, iframe HTML |
|
||||||
|
| VIM-03 | Get transcode status | vimeo_video_id | 200, status |
|
||||||
|
| VIM-04 | Delete Vimeo video | vimeo_video_id | 200 |
|
||||||
|
| VIM-05 | Pull upload | source URL | 201 |
|
||||||
|
| VIM-06 | TUS upload | file size + title | 201, upload link |
|
||||||
|
| VIM-07 | OEmbed | Vimeo URL | 200, embed data |
|
||||||
|
| VIM-08 | Vimeo service disabled | no config | graceful handling |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.19 CloudConvert (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Input | Expected |
|
||||||
|
|----|-----------|-------|----------|
|
||||||
|
| CC-01 | Video compression job | video file | compressed mp4 returned |
|
||||||
|
| CC-02 | Image optimization job | jpg file | webp returned |
|
||||||
|
| CC-03 | Job timeout | slow job | error after 30min (video) / 5min (image) |
|
||||||
|
| CC-04 | Job failure | invalid file | error with task message |
|
||||||
|
| CC-05 | Service disabled | no config | nil service, callers skip |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.20 Activity Logs & Analytics (P3)
|
||||||
|
|
||||||
|
| ID | Test Case | Expected |
|
||||||
|
|----|-----------|----------|
|
||||||
|
| LOG-01 | Get activity logs (admin, filtered) | 200, filtered by action/resource/date |
|
||||||
|
| LOG-02 | Get activity log by ID | 200 |
|
||||||
|
| LOG-03 | Activity log recorded on course CRUD | log entry with actor, IP, UA |
|
||||||
|
| LOG-04 | Analytics dashboard (admin) | 200, dashboard data |
|
||||||
|
| LOG-05 | MongoDB logs endpoint | 200, log entries |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.21 Settings (P3)
|
||||||
|
|
||||||
|
| ID | Test Case | Expected |
|
||||||
|
|----|-----------|----------|
|
||||||
|
| SET-01 | Get settings (SUPER_ADMIN) | 200, settings list |
|
||||||
|
| SET-02 | Get settings (ADMIN) | 403 |
|
||||||
|
| SET-03 | Update settings | 200 |
|
||||||
|
| SET-04 | Get setting by key | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.22 Assessment (P2)
|
||||||
|
|
||||||
|
| ID | Test Case | Expected |
|
||||||
|
|----|-----------|----------|
|
||||||
|
| ASM-01 | Create assessment question | 201 |
|
||||||
|
| ASM-02 | List assessment questions | 200 |
|
||||||
|
| ASM-03 | Get assessment question by ID | 200 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Cross-Cutting Concerns
|
||||||
|
|
||||||
|
### 4.1 Input Validation
|
||||||
|
|
||||||
|
| ID | Test | Expected |
|
||||||
|
|----|------|----------|
|
||||||
|
| VAL-01 | Empty required fields | 400 with field-level errors |
|
||||||
|
| VAL-02 | Invalid email format | 400 |
|
||||||
|
| VAL-03 | Negative IDs in path params | 400 |
|
||||||
|
| VAL-04 | Non-numeric path params (e.g., `/courses/abc`) | 400 |
|
||||||
|
| VAL-05 | Malformed JSON body | 400 |
|
||||||
|
| VAL-06 | Oversized request body (>500MB) | 413 |
|
||||||
|
|
||||||
|
### 4.2 Cascading Deletes
|
||||||
|
|
||||||
|
| ID | Test | Expected |
|
||||||
|
|----|------|----------|
|
||||||
|
| CAS-01 | Delete category → courses deleted | verified via get |
|
||||||
|
| CAS-02 | Delete course → sub-courses deleted | verified via get |
|
||||||
|
| CAS-03 | Delete sub-course → videos deleted | verified via get |
|
||||||
|
| CAS-04 | Delete user → ratings deleted | verified via get |
|
||||||
|
|
||||||
|
### 4.3 Concurrency / Race Conditions
|
||||||
|
|
||||||
|
| ID | Test | Expected |
|
||||||
|
|----|------|----------|
|
||||||
|
| RACE-01 | Two users rating same target simultaneously | both succeed, no duplicates |
|
||||||
|
| RACE-02 | Same user submitting duplicate rating | upsert, only one record |
|
||||||
|
| RACE-03 | Concurrent subscription creation for same user | one succeeds or both idempotent |
|
||||||
|
|
||||||
|
### 4.4 File Upload Security
|
||||||
|
|
||||||
|
| ID | Test | Expected |
|
||||||
|
|----|------|----------|
|
||||||
|
| SEC-01 | Upload with spoofed content-type header (exe as jpg) | 400 (content sniffing rejects) |
|
||||||
|
| SEC-02 | Path traversal in filename | UUID-renamed, no traversal |
|
||||||
|
| SEC-03 | Null bytes in filename | handled safely |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Integration Test Scenarios (E2E Flows)
|
||||||
|
|
||||||
|
### Flow 1: Student Registration → Subscription → Learning
|
||||||
|
```
|
||||||
|
1. POST /user/register → PENDING
|
||||||
|
2. POST /user/verify-otp → ACTIVE
|
||||||
|
3. POST /auth/customer-login → tokens
|
||||||
|
4. GET /subscription-plans → choose plan
|
||||||
|
5. POST /subscriptions/checkout → payment URL
|
||||||
|
6. POST /payments/webhook → payment SUCCESS
|
||||||
|
7. GET /subscriptions/status → ACTIVE
|
||||||
|
8. GET /course-management/learning-tree → courses
|
||||||
|
9. GET /sub-courses/:id/videos/published → watch
|
||||||
|
10. POST /ratings → rate course
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 2: Admin Content Management
|
||||||
|
```
|
||||||
|
1. POST /auth/admin-login → tokens
|
||||||
|
2. POST /course-management/categories → create category
|
||||||
|
3. POST /course-management/courses → create course
|
||||||
|
4. POST /courses/:id/thumbnail → upload thumbnail
|
||||||
|
5. POST /course-management/sub-courses → create sub-course
|
||||||
|
6. POST /sub-courses/:id/thumbnail → upload thumbnail
|
||||||
|
7. POST /course-management/videos/upload → upload video (CloudConvert → Vimeo)
|
||||||
|
8. PUT /videos/:id/publish → publish
|
||||||
|
9. GET /activity-logs → verify all actions logged
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 3: Issue Resolution
|
||||||
|
```
|
||||||
|
1. Student: POST /issues → bug report
|
||||||
|
2. Admin: GET /issues → see all
|
||||||
|
3. Admin: PATCH /issues/:id/status → resolved
|
||||||
|
4. Student: GET /notifications → sees status update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 4: Team Onboarding
|
||||||
|
```
|
||||||
|
1. SUPER_ADMIN: POST /team/members → create instructor
|
||||||
|
2. Instructor: POST /team/login → tokens
|
||||||
|
3. Instructor: GET /team/me → profile
|
||||||
|
4. SUPER_ADMIN: PATCH /team/members/:id/status → active
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Performance & Load Testing
|
||||||
|
|
||||||
|
| Test | Target | Threshold |
|
||||||
|
|------|--------|-----------|
|
||||||
|
| Login throughput | POST /auth/customer-login | <200ms p95, 100 RPS |
|
||||||
|
| Course listing | GET /categories/:id/courses | <100ms p95 |
|
||||||
|
| Video listing | GET /sub-courses/:id/videos | <100ms p95 |
|
||||||
|
| Rating summary | GET /ratings/summary | <50ms p95 |
|
||||||
|
| Notification list | GET /notifications | <100ms p95 |
|
||||||
|
| File upload (5MB image) | POST /user/:id/profile-picture | <5s (without CC) |
|
||||||
|
| Concurrent ratings | 50 users rating simultaneously | no errors, correct counts |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Test Environment Requirements
|
||||||
|
|
||||||
|
- **Test database:** Isolated PostgreSQL instance, migrated with all 17 migrations
|
||||||
|
- **External services:** Vimeo, CloudConvert, ArifPay, AfroSMS — stub/mock in tests
|
||||||
|
- **Test data seeding:** Create fixtures for users (each role), categories, courses, sub-courses, plans
|
||||||
|
- **Cleanup:** Each test suite truncates its tables or runs in a transaction with rollback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Recommended Test Tooling
|
||||||
|
|
||||||
|
| Purpose | Tool |
|
||||||
|
|---------|------|
|
||||||
|
| Go tests | `testing` + `testify/assert` |
|
||||||
|
| HTTP testing | `net/http/httptest` or Fiber's `app.Test()` |
|
||||||
|
| DB integration | `testcontainers-go` (Postgres) |
|
||||||
|
| Mocking | `testify/mock` or `gomock` |
|
||||||
|
| API collection | Postman / Bruno (manual & CI) |
|
||||||
|
| Load testing | `k6` or `vegeta` |
|
||||||
|
| CI runner | GitHub Actions / Gitea Actions |
|
||||||
|
|
@ -51,38 +51,10 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
// Groups
|
// Groups
|
||||||
groupV1 := a.fiber.Group("/api/v1")
|
groupV1 := a.fiber.Group("/api/v1")
|
||||||
// tenant := groupV1.Group("/tenant/:tenant_slug", a.TenantMiddleware)
|
|
||||||
// groupV1.Get("/test", a.authMiddleware, a.authMiddleware, func(c *fiber.Ctx) error {
|
|
||||||
// fmt.Printf("\nTest Route %v\n", c.Route().Path)
|
|
||||||
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
|
||||||
// if !companyID.Valid {
|
|
||||||
// h.BadRequestLogger().Error("invalid company id")
|
|
||||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fmt.Printf("In the tenant auth test \n")
|
|
||||||
// return c.JSON(fiber.Map{
|
|
||||||
// "message": "Is is fine",
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// groupV1.Get("/", func(c *fiber.Ctx) error {
|
|
||||||
// fmt.Printf("\nTenant Route %v\n", c.Route().Path)
|
|
||||||
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
|
||||||
// if !companyID.Valid {
|
|
||||||
// h.BadRequestLogger().Error("invalid company id")
|
|
||||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
|
||||||
// }
|
|
||||||
// return c.JSON(fiber.Map{
|
|
||||||
// "message": "Company Tenant Active",
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
// Serve static files (profile pictures, etc.)
|
// Serve static files (profile pictures, etc.)
|
||||||
a.fiber.Static("/static", "./static")
|
a.fiber.Static("/static", "./static")
|
||||||
|
|
||||||
// Get S
|
|
||||||
groupV1.Get("/tenant", a.authMiddleware, h.GetTenantSlugByToken)
|
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
|
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
|
||||||
|
|
||||||
|
|
@ -100,42 +72,6 @@ func (a *App) initAppRoutes() {
|
||||||
// groupV1.Put("/assessment/questions/:id", h.UpdateAssessmentQuestion)
|
// groupV1.Put("/assessment/questions/:id", h.UpdateAssessmentQuestion)
|
||||||
// groupV1.Delete("/assessment/questions/:id", h.DeleteAssessmentQuestion)
|
// groupV1.Delete("/assessment/questions/:id", h.DeleteAssessmentQuestion)
|
||||||
|
|
||||||
// Start a new assessment attempt
|
|
||||||
// groupV1.Post(
|
|
||||||
// "/assessment/attempts",
|
|
||||||
// h.StartAssessmentAttempt,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Submit or update an answer
|
|
||||||
// groupV1.Post(
|
|
||||||
// "/assessment/attempts/:attempt_id/answers",
|
|
||||||
// h.SubmitAssessmentAnswer,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Final submission (locks answers)
|
|
||||||
// groupV1.Post(
|
|
||||||
// "/assessment/attempts/:attempt_id/submit",
|
|
||||||
// h.SubmitAssessmentAttempt,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Get attempt details
|
|
||||||
// groupV1.Get(
|
|
||||||
// "/assessment/attempts/:attempt_id",
|
|
||||||
// h.GetAssessmentAttemptByID,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Get final result + answers
|
|
||||||
// groupV1.Get(
|
|
||||||
// "/assessment/attempts/:attempt_id/result",
|
|
||||||
// h.GetAssessmentResult,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// // Evaluate attempt (admin / system)
|
|
||||||
// groupV1.Post(
|
|
||||||
// "/assessment/attempts/:attempt_id/evaluate",
|
|
||||||
// h.EvaluateAssessmentAttempt,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Course Management Routes
|
// Course Management Routes
|
||||||
|
|
||||||
// Course Categories
|
// Course Categories
|
||||||
|
|
@ -313,11 +249,6 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Post("/admin", a.authMiddleware, a.SuperAdminOnly, h.CreateAdmin)
|
groupV1.Post("/admin", a.authMiddleware, a.SuperAdminOnly, h.CreateAdmin)
|
||||||
groupV1.Put("/admin/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateAdmin)
|
groupV1.Put("/admin/:id", a.authMiddleware, a.SuperAdminOnly, h.UpdateAdmin)
|
||||||
|
|
||||||
// groupV1.Get("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.GetAllTransactionApprovers)
|
|
||||||
// groupV1.Get("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetTransactionApproverByID)
|
|
||||||
// groupV1.Post("/t-approver", a.authMiddleware, a.OnlyAdminAndAbove, h.CreateTransactionApprover)
|
|
||||||
// groupV1.Put("/t-approver/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.UpdateTransactionApprover)
|
|
||||||
|
|
||||||
//mongoDB logs
|
//mongoDB logs
|
||||||
groupV1.Get("/logs", a.authMiddleware, a.OnlyAdminAndAbove, handlers.GetLogsHandler(context.Background()))
|
groupV1.Get("/logs", a.authMiddleware, a.OnlyAdminAndAbove, handlers.GetLogsHandler(context.Background()))
|
||||||
|
|
||||||
|
|
@ -325,11 +256,6 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Get("/activity-logs", a.authMiddleware, a.OnlyAdminAndAbove, h.GetActivityLogs)
|
groupV1.Get("/activity-logs", a.authMiddleware, a.OnlyAdminAndAbove, h.GetActivityLogs)
|
||||||
groupV1.Get("/activity-logs/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetActivityLogByID)
|
groupV1.Get("/activity-logs/:id", a.authMiddleware, a.OnlyAdminAndAbove, h.GetActivityLogByID)
|
||||||
|
|
||||||
// groupV1.Get("/shop/transaction", a.authMiddleware, a.CompanyOnly, h.GetAllTransactions)
|
|
||||||
// groupV1.Get("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.GetTransactionByID)
|
|
||||||
// groupV1.Get("/shop/transaction/:id/bet", a.authMiddleware, a.CompanyOnly, h.GetShopBetByTransactionID)
|
|
||||||
// groupV1.Put("/shop/transaction/:id", a.authMiddleware, a.CompanyOnly, h.UpdateTransactionVerified)
|
|
||||||
|
|
||||||
// Notification Routes
|
// Notification Routes
|
||||||
groupV1.Post("/sendSMS", h.SendSingleAfroSMS)
|
groupV1.Post("/sendSMS", h.SendSingleAfroSMS)
|
||||||
groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
|
groupV1.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user