# Pagination and API Test Report

**Date:** March 2025  
**Test Suite:** `tests/test_all_apis.py`  
**Base URL:** `/api/v1`

---

## Summary

| Metric | Value |
|--------|-------|
| **Total APIs Tested** | 53 |
| **Status** | 53/53 PASS |
| **Regressions** | None |

---

## All APIs Tested

### Authentication (`/auth`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| POST | `/auth/register` | PASS | Register new user |
| POST | `/auth/login` | PASS | User login |
| POST | `/auth/send-otp` | PASS | Send OTP to phone |
| POST | `/auth/verify-otp` | — | (Not in test suite) |
| POST | `/auth/refresh-token` | PASS | Refresh access token |
| POST | `/auth/social-login` | — | (Not in test suite) |
| POST | `/auth/logout` | PASS | Logout user |
| DELETE | `/auth/account` | — | (Not in test suite) |
| GET | `/auth/me` | PASS | Get current user |
| PUT | `/auth/profile` | PASS | Update user profile |

### Admin (`/admin`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/admin/dashboard` | PASS | Admin dashboard stats |
| GET | `/admin/users` | PASS | List users (paginated) |
| GET | `/admin/users/{user_id}` | PASS | Get user by ID |
| PUT | `/admin/users/{user_id}/block` | PASS | Block/unblock user |
| GET | `/admin/drivers/pending` | PASS | Pending drivers (paginated) |
| PUT | `/admin/drivers/{driver_id}/verify` | PASS | Verify driver |
| GET | `/admin/withdrawals/pending` | PASS | Pending withdrawals (paginated) |
| PUT | `/admin/withdrawals/{withdrawal_id}/process` | PASS | Process withdrawal |
| POST | `/admin/promo-codes` | PASS | Create promo code |
| GET | `/admin/promo-codes` | PASS | List promo codes (paginated) |
| GET | `/admin/rides/live` | PASS | Live rides (paginated) |

### Dashboard (`/admin/dashboard`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/admin/dashboard/stats` | PASS | Dashboard statistics |
| GET | `/admin/dashboard/revenue` | PASS | Revenue chart data |
| GET | `/admin/dashboard/map` | PASS | Active ride locations |
| GET | `/admin/dashboard/drivers` | PASS | Driver performance list |
| GET | `/admin/dashboard/passengers` | PASS | Passenger list (paginated) |
| GET | `/admin/dashboard/driver-verifications` | PASS | Driver verifications (paginated) |
| GET | `/admin/dashboard/rides/stats` | PASS | Ride statistics |
| GET | `/admin/dashboard/rides` | PASS | Ride list (paginated) |
| GET | `/admin/dashboard/transactions` | PASS | Transaction list (paginated) |
| GET | `/admin/dashboard/reports/user-growth` | PASS | User growth chart |
| POST | `/admin/dashboard/driver-verifications/{id}/approve` | — | (Not in test suite) |
| POST | `/admin/dashboard/driver-verifications/{id}/reject` | — | (Not in test suite) |

### Rides (`/rides`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/rides/categories` | PASS | Vehicle categories |
| POST | `/rides/estimate` | PASS | Fare estimation |
| POST | `/rides/book` | PASS | Book a ride |
| GET | `/rides/{ride_id}` | PASS | Get ride details |
| GET | `/rides/{ride_id}/bids` | PASS | Ride bids (paginated) |
| POST | `/rides/{ride_id}/bids/{bid_id}/accept` | — | (Not in test suite) |
| PUT | `/rides/{ride_id}/status` | — | (Not in test suite) |
| POST | `/rides/{ride_id}/cancel` | — | (Not in test suite) |

### Passenger (`/passenger`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/passenger/profile` | PASS | Get passenger profile |
| PUT | `/passenger/profile` | PASS | Update passenger profile |
| GET | `/passenger/rides` | PASS | Ride history (paginated) |
| GET | `/passenger/rides/active` | PASS | Current active ride |

### Payment (`/payments`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/payments/wallet` | PASS | Wallet balance |
| POST | `/payments/wallet/topup` | PASS | Top up wallet |
| GET | `/payments/transactions` | PASS | Transaction history (paginated) |
| POST | `/payments/withdraw` | — | (Not in test suite) |
| GET | `/payments/withdrawals` | PASS | Withdrawal history (paginated) |

### Driver (`/driver`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/driver/profile` | PASS | Driver profile |
| GET | `/driver/account-settings` | PASS | Account settings |
| PUT | `/driver/account-settings` | PASS | Update account settings |
| PUT | `/driver/profile` | — | (Not in test suite) |
| PUT | `/driver/location` | PASS | Update location |
| PUT | `/driver/status` | PASS | Update online status |
| POST | `/driver/documents` | — | (Not in test suite) |
| GET | `/driver/earnings` | PASS | Driver earnings |
| GET | `/driver/rides/nearby` | PASS | Nearby ride requests |
| POST | `/driver/rides/{ride_id}/bid` | — | (Not in test suite) |
| GET | `/driver/rides/active` | PASS | Active ride |

### Offer Profiles (`/driver/offer-profiles`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/driver/offer-profiles` | PASS | List profiles (paginated) |
| PUT | `/driver/offer-profiles/priority` | PASS | Update priority order |
| POST | `/driver/offer-profiles` | PASS | Create profile |
| GET | `/driver/offer-profiles/{id}` | — | (Not in test suite) |
| PUT | `/driver/offer-profiles/{id}` | — | (Not in test suite) |
| DELETE | `/driver/offer-profiles/{id}` | — | (Not in test suite) |

### Configuration (`/config`)

| Method | Endpoint | Status | Description |
|--------|----------|--------|-------------|
| GET | `/config/settings` | PASS | System settings |
| GET | `/config/map-providers` | PASS | Map provider configs |

---

## Fixes Applied

### 1. Auth POST `/register` — bcrypt/passlib compatibility

**Issue:** `400 "password cannot be longer than 72 bytes"` due to passlib/bcrypt 5.x incompatibility.

**Fix:** Replaced passlib with direct bcrypt usage in `app/services/auth_service.py`:
- `hash_password`: use `bcrypt.hashpw()` and `bcrypt.gensalt()`
- `verify_password`: use `bcrypt.checkpw()`

---

### 2. Dashboard GET `/rides/stats` — missing import

**Issue:** `NameError: name 'or_' is not defined` in `get_ride_stats`.

**Fix:** Added `or_` to SQLAlchemy import in `app/services/dashboard.py`:
```python
from sqlalchemy import select, func, desc, and_, or_
```

---

### 3. Driver GET `/profile` — dynamic relationship

**Issue:** `'Driver.documents' does not support object population - eager loading cannot be applied.`

**Fix:** In `app/routes/driver.py`, removed `selectinload(Driver.documents)` and load documents with a separate query:
```python
doc_query = select(DriverDocument).where(DriverDocument.driver_id == driver.id)
doc_result = await db.execute(doc_query)
documents = doc_result.scalars().all()
```

---

### 4. Rides GET `/{ride_id}` — async lazy loading

**Issue:** `MissingGreenlet` when accessing `ride.driver.user` (lazy load in async context).

**Fix:** In `app/services/ride_service.py`, added nested eager loading in `get_ride_by_id`:
```python
selectinload(Ride.driver).selectinload(Driver.user),
selectinload(Ride.driver).selectinload(Driver.current_vehicle),
selectinload(Ride.rating)
```

---

### 5. Passenger GET `/rides/active` — multiple rows

**Issue:** `Multiple rows were found when one or none was required` when passenger had multiple active rides.

**Fix:** In `app/routes/passenger.py`, added `.limit(1)` and switched to `result.scalars().first()`:
```python
).order_by(Ride.created_at.desc()).limit(1)
result = await db.execute(query)
ride = result.scalars().first()
```

---

### 6. Offer Profiles PUT `/priority` — function call

**Issue:** `TypeError: list_offer_profiles() got multiple values for argument 'page'`.

**Fix:** In `app/routes/offer_profile.py`, corrected call to use keyword arguments:
```python
return await list_offer_profiles(page=1, limit=100, driver=driver, db=db)
```

---

## Edge Case Behavior

### Pagination limits

Paginated endpoints enforce `limit` bounds via FastAPI `Query`:

| Constraint | Value | Behavior |
|------------|-------|----------|
| `limit` minimum | 1 | `limit=0` or negative → **422 Unprocessable Entity** |
| `limit` maximum | 100 | `limit>100` → **422 Unprocessable Entity** |
| `page` minimum | 1 | `page=0` or negative → **422 Unprocessable Entity** |

**Affected endpoints:**
- Admin: `/users`, `/drivers/pending`, `/withdrawals/pending`, `/promo-codes`, `/rides/live`
- Dashboard: `/passengers`, `/driver-verifications`, `/rides`, `/transactions`
- Passenger: `/rides`
- Payment: `/transactions`, `/withdrawals`
- Rides: `/{ride_id}/bids`
- Offer profiles: list endpoint

### Dashboard-specific limits

- `/admin/dashboard/drivers`: `limit` 1–100 (default 10)
- Other dashboard paginated endpoints: `limit` 1–100 (default 10)

### Example 422 response (limit > max)

```json
{
  "error": "ValidationError",
  "message": "Invalid input data",
  "details": [
    {
      "field": "limit",
      "message": "ensure this value is less than or equal to 100",
      "type": "less_than_equal"
    }
  ],
  "status_code": 422
}
```

---

## Running the Tests

```bash
cd /path/to/Backend-Code
source venv/bin/activate
PYTHONPATH=. python tests/test_all_apis.py
```

**Prerequisites:**
- Database initialized (development environment)
- Test users exist: admin (id=1), driver (id=2), passenger (id=17)
