# Backend manual test playbook

Base URL: `{{base}}` (e.g. `http://localhost/api`). All authenticated requests use header:

`Authorization: Bearer <token>`

Auth tokens are **opaque API tokens** (SHA-256 stored in `auth_tokens`), not JWT.

---

## Global preconditions

1. Run migrations and seeders: `php artisan migrate --seed` (includes demo users if configured).
2. Create an admin: `POST {{base}}/auth/register` or use seeded admin, then promote via admin API or DB.
3. Admin token: `POST {{base}}/auth/login` with admin email/password, save `token`.

---

## 1. Authentication

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-AUTH-01** | Pass | `POST /auth/login` JSON `{"email":"<valid>","password":"<valid>"}` | HTTP 200; body has `token`, `user`, `user.id` |
| **TC-AUTH-02** | Pass | `POST /auth/login` with wrong password | HTTP **401** (`AuthenticationException`); no `token` |
| **TC-AUTH-03** | Pass | `PATCH /admin/users/{id}/status` body `{"status":"blocked"}` (admin token), then login as that user | HTTP 403 on login; message indicates inactive/blocked account |
| **TC-AUTH-04** | Pass | `GET /auth/me` with no `Authorization` header | HTTP 401 |
| **TC-AUTH-05** | Pass | Login, `POST /auth/logout` with Bearer token, then `GET /auth/me` with same token | First 200; after logout `GET /auth/me` returns 401 |

**Example (TC-AUTH-01):**

```http
POST {{base}}/auth/login
Content-Type: application/json

{"email":"user1@example.com","password":"password123"}
```

**SQL (optional):** `SELECT id, user_id, token_hash FROM auth_tokens WHERE user_id = ?;` after login (hash present); after logout row deleted.

---

## 2. Deposits

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-DEP-01** | Pass | User token: `POST /user/deposits` `{"amount":100,"channel_type":"web3","tx_hash":"0xunique1..."}` | 201; `data.status` = `approved`; `transactions` row credit `source_type` deposit, `metadata` contains `deposit_id` |
| **TC-DEP-02** | Pass | `POST /user/deposits` `{"amount":50,"channel_type":"crypto_manual","tx_hash":"0xmanual1..."}` | 201; `pending`; no credit transaction for that deposit |
| **TC-DEP-03** | Pass | Repeat **same** `tx_hash` as TC-DEP-02 | HTTP 422; message indicates duplicate; no second deposit row |
| **TC-DEP-04** | Pass | `POST /user/deposits` `{"amount":25,"channel_type":"other_manual","metadata":{"note":"bank"}}` | 201; `pending`; `metadata` persisted |
| **TC-DEP-05** | Pass | Create pending manual deposit; `PATCH /admin/deposits/{deposit}` body `{"action":"approve"}` (admin Bearer) | `approved`; ledger credit; amount matches approved |
| **TC-DEP-06** | Pass | Pending deposit; `PATCH /admin/deposits/{deposit}` `{"action":"reject"}` | `rejected`; no new credit transaction |
| **TC-DEP-07** | Pass | After TC-DEP-01 | `SELECT * FROM transactions WHERE user_id=? AND source_type='deposit' AND JSON_EXTRACT(metadata,'$.deposit_id') = <id>` (or app-specific metadata shape) |
| **TC-DEP-08** | Pass | `PATCH /admin/deposits/{deposit}` `{"action":"approve_partial","approved_amount":60}` | Credit 60; `metadata.partial` true; ledger matches 60 |

---

## 3. Investments

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-INV-01** | Pass | Fund via web3 deposit; `POST /user/investments` `{"package_id":1,"amount":100}` | 201; `investments` row correct `package_id`, `amount` |
| **TC-INV-02** | Pass | Amount outside package min/max | HTTP 422; no new row |
| **TC-INV-03** | Pass | Multiple valid `POST /user/investments` | Multiple rows; `GET /user/investments` lists all |
| **TC-INV-04** | Pass | Invest more than `deposit_wallet` | HTTP 422 |
| **TC-INV-05** | Pass | When per-investment ROI reaches 2× cap (engine + cap), investment may be marked `completed` | `investments.status` = `completed`; ROI display stops increasing for that plan |

---

## 4. ROI engine (unit + integration)

| TC | Status | Verification |
|----|--------|--------------|
| **TC-ROI-01–04** | Pass | Covered by `tests/Unit/RoiEngineTest.php` (multi-segment); manual: `GET /user/wallets` after seeded configs |
| **TC-ROI-02** | Pass | ROI engine: `now` before `start_date + startAfterDays` → 0 |
| **TC-ROI-03** | Pass | Per-investment cap: `min(computed_roi, 2× investment.amount)` in `IncomeComputationService` |
| **TC-ROI-05** | Pass | User `is_working_id=true` → `GET /user/income` ROI component 0 |
| **TC-ROI-06** | Pass | `roi_enabled=false` → 0 ROI |
| **TC-ROI-07** | Pass | Change `roi_configs` effective dates; recompute |
| **TC-ROI-08** | Partial | Withdrawal reduces “available” via wallet summary; ROI cap uses per-investment logic |

---

## 5. Direct income

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-DIR-01** | Pass | Sponsor A, referral B; B creates investment | `direct_incomes` row for A; amount = % of investment (see `config/compensation.php`) |
| **TC-DIR-02** | Pass | Multiple referrals invest | Multiple rows; sums match |
| **TC-DIR-03** | Pass | Large direct vs 3× cap | Amount capped; `status` or metadata may reflect partial |

---

## 6. Level ROI income

| TC | Status | Notes |
|----|--------|-------|
| **TC-LVL-01–06** | Pass | `level_incomes` rows when upline chain + configs exist; downline working ID skips; caps via `CapEngine` |

---

## 7. Matching engine

| TC | Status | Notes |
|----|--------|-------|
| **TC-MATCH-01–04** | Pass | `tests/Unit/MatchingEngineTest.php`; inputs as array of leg amounts |
| **TC-MATCH-05** | Pass | Virtual legs included in `MatchingEngine::computeWithVirtualRestrictions` inputs |

---

## 8. Reward system

| TC | Status | Notes |
|----|--------|-------|
| **TC-RWD-01** | Pass | When matching crosses `reward_configs.matching_required`, `user_rewards` insert |
| **TC-RWD-02** | Pass | Unique `(user_id, reward_config_id)` — second grant blocked |
| **TC-RWD-03** | Pass | Partial/capped amounts within 3× total cap |

---

## 9. Rank system

| TC | Status | Notes |
|----|--------|-------|
| **TC-RANK-01–04** | Partial | Rank achievement flow may be seed/admin-driven; income via `RankIncomeEngine` over `duration_days` |

---

## 10. Transfers

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-TRF-01** | Pass | `POST /user/transfers` `{"target":"<email>","amount":10}` | Debit + credit `transactions` pair |
| **TC-TRF-02** | Pass | Invalid target | 404/422; no txn |
| **TC-TRF-03** | Pass | Amount > deposit_wallet | 422 |

---

## 11. Withdrawals

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-WDR-01** | Pass | `POST /user/withdrawals` valid | Row `pending`; fee/net in row |
| **TC-WDR-02** | Pass | Below `config('compensation.withdrawal.min_amount')` | 422 |
| **TC-WDR-03** | Pass | Compare `fee`, `net_amount` to 5% rule | Matches config |
| **TC-WDR-04** | Pass | `PATCH /admin/withdrawals/{withdrawal}` `{"action":"approve"}` | `approved`; ledger debit on `transactions`; balances consistent |

---

## 12. Virtual leg

| TC | Status | Notes |
|----|--------|-------|
| **TC-VIRT-01–03** | Partial | Data via `virtual_legs` / `user_leg_stats`; expiry filtering may need explicit scheduler |

---

## 13. Eligibility engine

| TC | Status | Notes |
|----|--------|-------|
| **TC-ELIG-01–03** | Pass | Covered by engines + `IncomeComputationService`; working ID / roi_enabled flags |

---

## 14. Cap system

| TC | Status | Notes |
|----|--------|-------|
| **TC-CAP-01** | Pass | 2× ROI per investment in computation |
| **TC-CAP-02** | Pass | `CapEngine::totalIncomeCap` / `applyTotalIncomeCap` |
| **TC-CAP-03** | Pass | Mixed incomes; direct+reward sums in denominator |

---

## Fund Transfer (FT) wallet

FT is a ledger-backed balance (`transactions.wallet_type` = `fund_transfer`). Users **explicitly** move value into FT from deposit, ROI, or bonus; **peer transfers** debit/credit FT only. Investments can use **deposit** (default), **FT only**, or a **split**.

| TC | Status | Steps | Assertions |
|----|--------|-------|------------|
| **TC-FT-01** | | Approve deposit so `deposit_wallet` &gt; 0; `POST /user/wallets/ft/fund` `{"from":"deposit","amount":50}` | HTTP 201; `data.ft_balance` increases; `GET /user/wallets` shows lower `deposit_wallet`, higher `ft_wallet`, `available_deposit_for_ft` reduced |
| **TC-FT-02** | | With ROI/bonus aggregate &gt; 0, `POST /user/wallets/ft/fund` `{"from":"roi"|"bonus","amount":...}` within available | 201; FT balance up; cumulative move reflected (metadata on `user_stats` for ROI/bonus) |
| **TC-FT-03** | | `POST /user/transfers` to another user for amount ≤ `ft_wallet` | Receiver `ft_wallet` credited; sender debited |
| **TC-FT-04** | | `POST /user/investments` `{"package_id":1,"amount":100,"source":"ft"}` with sufficient FT | 201; `fund_transfer` debit `sub_status` `fund_out_investment` |
| **TC-FT-05** | | `POST /user/investments` with `"source":"split","from_deposit":40,"from_ft":60,"amount":100` | 201; both wallet debits; sums match |
| **TC-FT-06** | | Transfer or invest with amount &gt; `ft_wallet` where FT is required | HTTP 422 |

**Example (TC-FT-01):**

```http
POST {{base}}/user/wallets/ft/fund
Authorization: Bearer <token>
Content-Type: application/json

{"from":"deposit","amount":50}
```

---

## Final SQL snippets (MySQL)

```sql
-- Deposits + tx for user
SELECT d.*, t.id AS txn_id, t.amount AS txn_amt, t.direction
FROM deposits d
LEFT JOIN transactions t ON t.user_id = d.user_id AND t.source_type = 'deposit'
  AND JSON_UNQUOTE(JSON_EXTRACT(t.metadata, '$.deposit_id')) = d.id
WHERE d.user_id = ?;

-- Auth tokens
SELECT * FROM auth_tokens WHERE user_id = ?;

-- Direct / level income
SELECT * FROM direct_incomes WHERE user_id = ?;
SELECT * FROM level_incomes WHERE user_id = ?;
```

---

## Blocked / environment notes

- Use **HTTPS** in production; local `http://localhost` is fine.
- **Mail** not required for these API tests.
- If a TC shows **Blocked** in an older matrix, re-check this file after pulling latest `main` — implementation may have caught up.
