549 lines
12 KiB
Markdown
549 lines
12 KiB
Markdown
# AvaAir E2E Flight Flow Test Suite
|
||
|
||
This test suite exercises the **end-to-end customer journey** for AvaAir’s domestic flights (THR → MHD) using real API calls against a live environment (staging/production-like).
|
||
|
||
It is intentionally designed to:
|
||
|
||
* Validate **critical customer flows**: airport discovery, rules endpoints, flight search, booking, ticket issuance, and refund.
|
||
* Run both **interactively** (developer manually enters OTP) and **fully automated** (CI with pre-configured OTP).
|
||
* Be **readable and presentable** as a “production-grade” test harness.
|
||
|
||
---
|
||
|
||
## 1. High-level Architecture
|
||
|
||
The test suite is composed of:
|
||
|
||
* `conftest.py`
|
||
Global configuration and reusable fixtures shared across tests:
|
||
|
||
* Environment-driven configuration.
|
||
* Customer authentication (OTP flow).
|
||
* Authorization header fixture.
|
||
* Canonical THR → MHD search parameters.
|
||
* A small `api_wait` pacing helper for stabilizing async flows.
|
||
|
||
* `test_flight_thr_mhd_e2e.py`
|
||
Actual test cases:
|
||
|
||
* IATA / airport discovery tests.
|
||
* Rules endpoints sanity checks.
|
||
* A full end-to-end scenario for:
|
||
|
||
* Search → Book → Wallet check → Issue → Refund penalty → Refund → Post-refund validation.
|
||
|
||
The design intentionally centralizes configuration and “plumbing” in `conftest.py` so that individual tests remain concise and focused on business logic.
|
||
|
||
---
|
||
|
||
## 2. Environment & Configuration
|
||
|
||
All configuration is driven through environment variables with reasonable defaults, so the suite can be used:
|
||
|
||
* Quickly on a developer machine (with defaults).
|
||
* Safely in CI (with explicit overrides).
|
||
|
||
### 2.1 Base API URL
|
||
|
||
```bash
|
||
API_BASE_URL=http://192.168.19.19:8200/api/v1
|
||
```
|
||
|
||
* Default: `http://192.168.19.19:8200/api/v1`
|
||
* Use this to point the suite to **staging**, **pre-prod**, or **production**.
|
||
|
||
---
|
||
|
||
### 2.2 Test User – Mobile & OTP
|
||
|
||
```bash
|
||
TEST_MOBILE_COUNTRY_CODE=98
|
||
TEST_MOBILE_NUMBER=9014332990
|
||
TEST_OTP_CODE=<optional pre-shared OTP>
|
||
```
|
||
|
||
* `TEST_MOBILE_COUNTRY_CODE` and `TEST_MOBILE_NUMBER` define the test customer account used for OTP login.
|
||
* `TEST_OTP_CODE` controls how OTP is resolved:
|
||
|
||
#### Interactive mode (local dev)
|
||
|
||
If `TEST_OTP_CODE` is **not** set:
|
||
|
||
* The suite sends an OTP to the configured mobile.
|
||
|
||
* The developer is prompted in the terminal:
|
||
|
||
```text
|
||
➡️ Enter OTP received on the test device:
|
||
```
|
||
|
||
* The entered OTP is then used for `/users/otp/check`.
|
||
|
||
#### Non-interactive mode (CI / automated)
|
||
|
||
If `TEST_OTP_CODE` **is** set:
|
||
|
||
* The suite **does not** prompt.
|
||
* The value is used directly in the OTP verify call.
|
||
* This enables fully headless execution in pipelines.
|
||
|
||
---
|
||
|
||
### 2.3 Booking Contact Info
|
||
|
||
```bash
|
||
TEST_BOOK_EMAIL=iamrezazoom@gmail.com
|
||
TEST_BOOK_PHONE=9014332990
|
||
```
|
||
|
||
* Email and phone number used in `/customer/flight/book` payloads.
|
||
* Defaults match the test user’s profile but can be overridden in more advanced setups.
|
||
|
||
---
|
||
|
||
### 2.4 Traveler Profile
|
||
|
||
These fields define the passive traveler object used during booking:
|
||
|
||
```bash
|
||
TEST_TRAVELER_FIRST_NAME=REZA
|
||
TEST_TRAVELER_LAST_NAME=ESMAEILI
|
||
TEST_TRAVELER_GENDER=male
|
||
TEST_TRAVELER_NATIONAL_CODE=0890500363
|
||
TEST_TRAVELER_DATE_OF_BIRTH=1998-05-20
|
||
TEST_TRAVELER_PLACE_OF_BIRTH=IRN
|
||
```
|
||
|
||
All of these have sensible defaults in `conftest.py`, but **in a real production-like setup** you should:
|
||
|
||
* Override them with a dedicated test passenger identity.
|
||
* Ensure the national ID and passport-related fields are valid for your environment.
|
||
|
||
---
|
||
|
||
## 3. Core Fixtures (conftest.py)
|
||
|
||
### 3.1 `_today_plus_days(days: int) -> str`
|
||
|
||
Utility that returns an ISO date (`YYYY-MM-DD`) relative to “today”:
|
||
|
||
* Used to always search for flights **7 days in the future**.
|
||
* Prevents hitting past or expired inventory.
|
||
|
||
```python
|
||
def _today_plus_days(days: int) -> str:
|
||
return (dt.date.today() + dt.timedelta(days=days)).isoformat()
|
||
```
|
||
|
||
---
|
||
|
||
### 3.2 `api_wait` Fixture
|
||
|
||
A small timing helper used to **pace** calls in E2E flows:
|
||
|
||
```python
|
||
@pytest.fixture
|
||
def api_wait():
|
||
import time
|
||
def _do(seconds: float = 1.0) -> None:
|
||
time.sleep(seconds)
|
||
return _do
|
||
```
|
||
|
||
Why it exists:
|
||
|
||
* Some backend operations are not purely synchronous:
|
||
|
||
* ticket issuance,
|
||
* wallet balance updates,
|
||
* ticket status changes.
|
||
* Adding short, explicit waits between operations makes the E2E flow **deterministic** even on real environments.
|
||
|
||
Usage example (from the E2E test):
|
||
|
||
```python
|
||
res_issue = requests.post(...)
|
||
|
||
assert res_issue.status_code == 204
|
||
api_wait(2.0) # Let backend finalize issuance before querying tickets
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 `customer_auth` Fixture
|
||
|
||
This is the **entry point** into the tested system from a customer perspective.
|
||
|
||
High-level behavior:
|
||
|
||
1. Sends OTP request:
|
||
|
||
```http
|
||
POST /users/otp/request
|
||
```
|
||
2. Resolves OTP from:
|
||
|
||
* `TEST_OTP_CODE` (if set), or
|
||
* interactive `input()` prompt.
|
||
3. Validates OTP via:
|
||
|
||
```http
|
||
POST /users/otp/check
|
||
```
|
||
4. Asserts the response contains:
|
||
|
||
* `auth_token`
|
||
* `refresh_token`
|
||
* `user`
|
||
5. Returns an auth context dict:
|
||
|
||
```python
|
||
{
|
||
"auth_token": "...",
|
||
"refresh_token": "...",
|
||
"user": {...},
|
||
}
|
||
```
|
||
|
||
If anything in this flow changes on the backend (contract change, field rename), the fixture fails **fast** with a clear assertion.
|
||
|
||
---
|
||
|
||
### 3.4 `auth_header` Fixture
|
||
|
||
Thin wrapper that exposes the **canonical Authorization header**:
|
||
|
||
```python
|
||
@pytest.fixture(scope="session")
|
||
def auth_header(customer_auth):
|
||
return {
|
||
"Authorization": f"Bearer {customer_auth['auth_token']}",
|
||
"accept": "application/json",
|
||
}
|
||
```
|
||
|
||
All tests consume this instead of re-implementing their own header logic.
|
||
|
||
---
|
||
|
||
### 3.5 `thr_to_mhd_search_params` Fixture
|
||
|
||
Single source of truth for THR → MHD search parameters:
|
||
|
||
```python
|
||
@pytest.fixture(scope="session")
|
||
def thr_to_mhd_search_params():
|
||
return {
|
||
"origin_iata": "THR",
|
||
"destination_iata": "MHD",
|
||
"flight_date": _today_plus_days(7),
|
||
"page": 1,
|
||
"page_size": 20,
|
||
"sort_option": "lowest-price",
|
||
}
|
||
```
|
||
|
||
Used by the E2E test to:
|
||
|
||
* Always search a known route (THR → MHD).
|
||
* Always target `today + 7` days.
|
||
* Always sort by `lowest-price` to make the first itinerary deterministic.
|
||
|
||
---
|
||
|
||
## 4. Test Cases Overview (`test_flight_thr_mhd_e2e.py`)
|
||
|
||
### 4.1 IATA / Airport Discovery Tests
|
||
|
||
These cover the **airport search layer** exposed to the customer:
|
||
|
||
#### `test_iata_domestic_default_list`
|
||
|
||
* **Endpoint**: `GET /customer/iata`
|
||
* **Parameters**:
|
||
|
||
* `keyword = ""`
|
||
* `is_international = false`
|
||
* **Validations**:
|
||
|
||
* HTTP 200.
|
||
* `data` is a non-empty list.
|
||
* Both `THR` and `MHD` are present in the code list.
|
||
|
||
Purpose:
|
||
Ensures the default domestic IATA catalog is healthy and includes key airports.
|
||
|
||
---
|
||
|
||
#### `test_iata_search_origin_tehran`
|
||
|
||
* **Endpoint**: `GET /customer/iata`
|
||
* **Parameters**:
|
||
|
||
* `keyword = "tehran"`
|
||
* `is_international = false`
|
||
* **Validations**:
|
||
|
||
* HTTP 200.
|
||
* `data` is a non-empty list.
|
||
* `THR` appears in results.
|
||
|
||
Purpose:
|
||
Validates keyword search for origin airports (Tehran).
|
||
|
||
---
|
||
|
||
#### `test_iata_search_destination_mashhad_excluding_origin`
|
||
|
||
* **Endpoint**: `GET /customer/iata`
|
||
* **Parameters**:
|
||
|
||
* `keyword = "mashhad"`
|
||
* `is_international = false`
|
||
* `other_iata_code = "THR"`
|
||
* **Validations**:
|
||
|
||
* HTTP 200.
|
||
* `data` is a non-empty list.
|
||
* `MHD` appears.
|
||
* `THR` **does not** appear (origin must be excluded).
|
||
|
||
Purpose:
|
||
Confirms that destination search respects the “other airport” exclusion logic.
|
||
|
||
---
|
||
|
||
### 4.2 General & Refund Rules Test
|
||
|
||
#### `test_general_and_refund_rules`
|
||
|
||
Endpoints:
|
||
|
||
* `GET /customer/setting/get/general-rules`
|
||
* `GET /customer/setting/get/refund-rules`
|
||
|
||
Validations:
|
||
|
||
* `general-rules`:
|
||
|
||
* `data.general-rules` exists and is non-empty.
|
||
* `refund-rules`:
|
||
|
||
* `data.refund_rules.domestic.data` is a non-empty list of objects.
|
||
* `data.refund_rules.international.data` the same.
|
||
* First item in each has `description` and `penalty`.
|
||
|
||
Purpose:
|
||
Quickly validates that the **configuration surface** (general rules and refund rules shown to customers) is available and structurally sound.
|
||
|
||
---
|
||
|
||
### 4.3 E2E: THR → MHD Single Adult Flow
|
||
|
||
#### `@pytest.mark.e2e test_thr_mhd_single_adult_book_issue_refund_e2e(...)`
|
||
|
||
This is the **flagship scenario**.
|
||
|
||
It simulates a real customer:
|
||
|
||
1. **Search flights (THR → MHD, D+7)**
|
||
|
||
* `POST /customer/flight/search`
|
||
* Validates:
|
||
|
||
* HTTP 200.
|
||
* Non-empty `itinerary_list`.
|
||
* First itinerary has:
|
||
|
||
* valid `flight_id`,
|
||
* at least one segment,
|
||
* segment from `THR` to `MHD`,
|
||
* `total_fare > 0`.
|
||
|
||
2. **Book the first itinerary**
|
||
|
||
* `POST /customer/flight/book`
|
||
* Payload includes:
|
||
|
||
* Contact info (`email`, `phone_number`).
|
||
* Single ADT traveler (driven by env / defaults).
|
||
* Validates:
|
||
|
||
* HTTP 200.
|
||
* `id` (booking_id) is positive.
|
||
* `expires_at > 0`.
|
||
* `price > 0` (booking_price).
|
||
|
||
3. **Verify wallet credit**
|
||
|
||
* `GET /admin/user/credit`
|
||
* Validates:
|
||
|
||
* HTTP 200.
|
||
* `credit` is numeric.
|
||
* `credit >= booking_price`.
|
||
* This step guarantees the issue step using wallet credit will succeed and is not blocked by insufficient balance.
|
||
|
||
4. **Issue the ticket using wallet credit**
|
||
|
||
* `POST /customer/flight/book/{booking_id}/issue`
|
||
* Payload:
|
||
|
||
* `pay_with_credit = true`
|
||
* `pay_for = "ticket"`
|
||
* `platform = "mobile"`
|
||
* Validates:
|
||
|
||
* HTTP 204 (No Content).
|
||
* Followed by `api_wait(2.0)` to allow backend to finalize issuance.
|
||
|
||
5. **Fetch active tickets and locate the issued one**
|
||
|
||
* `GET /customer/ticket` with:
|
||
|
||
* `is_past = false`
|
||
* `statuses = confirmed`
|
||
* Validates:
|
||
|
||
* HTTP 200.
|
||
* Non-empty ticket list.
|
||
* Ticket with `total == booking_price` exists.
|
||
* Extracts:
|
||
|
||
* `ticket_id`
|
||
* `ticket_traveler_id` (first traveler)
|
||
* `ticket_segment_id` (first segment)
|
||
|
||
6. **Calculate refund penalty**
|
||
|
||
* `POST /customer/flight/refund-penalty`
|
||
* Payload:
|
||
|
||
* `ticket_segment_id`
|
||
* `[ticket_traveler_id]`
|
||
* Validations (for this no-penalty scenario):
|
||
|
||
* HTTP 200.
|
||
* Non-empty `data` array.
|
||
* First item:
|
||
|
||
* `payable > 0`
|
||
* `amount_that_should_pay_back_to_passenger > 0`
|
||
* `penalty_percent == 0`
|
||
* `amount_that_should_pay_back_to_passenger == booking_price`
|
||
* `ticket_traveler_id` matches the one sent.
|
||
|
||
7. **Perform refund**
|
||
|
||
* `POST /customer/flight/refund`
|
||
* Payload:
|
||
|
||
* same `ticket_segment_id` and `ticket_traveler_id`.
|
||
* Validates:
|
||
|
||
* HTTP 200.
|
||
* Followed by `api_wait(2.0)` to allow status propagation.
|
||
|
||
8. **Validate post-refund ticket state**
|
||
|
||
* `GET /customer/ticket` with:
|
||
|
||
* `is_past = true`
|
||
* `statuses = confirmed,refunded_partially,refunded`
|
||
* Locates the same `ticket_id`.
|
||
* Validates:
|
||
|
||
* `refund_status` is in `("refunded", "refunded_partially")`.
|
||
|
||
Throughout the flow, the test logs each step with clear `[STEP X]` messages, making it easy to follow in CI logs or during a live demo.
|
||
|
||
---
|
||
|
||
## 5. Running the Tests
|
||
|
||
### 5.1 Install dependencies
|
||
|
||
```bash
|
||
pip install pytest requests
|
||
```
|
||
|
||
(Or include them in your project’s `requirements.txt` / `poetry` / `pipenv`.)
|
||
|
||
---
|
||
|
||
### 5.2 Run all tests
|
||
|
||
```bash
|
||
pytest -q -s
|
||
```
|
||
|
||
* `-s` keeps the step-by-step console output:
|
||
|
||
* OTP flow logs,
|
||
* `[STEP X]` E2E narration.
|
||
|
||
---
|
||
|
||
### 5.3 Run only the E2E scenario
|
||
|
||
The E2E test is marked with `@pytest.mark.e2e`. To run only that:
|
||
|
||
```bash
|
||
pytest -q -s -m e2e
|
||
```
|
||
|
||
To avoid Pytest’s “unknown mark” warning, add a `pytest.ini` in the repo root:
|
||
|
||
```ini
|
||
# pytest.ini
|
||
[pytest]
|
||
markers =
|
||
e2e: end-to-end tests that hit real external services (search, book, issue, refund)
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Production Safety Notes
|
||
|
||
Because this suite can be pointed to a **live environment**:
|
||
|
||
* It **does**:
|
||
|
||
* Create real bookings.
|
||
* Issue real tickets.
|
||
* Immediately refund them.
|
||
|
||
* It **assumes**:
|
||
|
||
* The test account’s wallet has enough credit.
|
||
* The fare and policy selected result in **zero penalty** for the scenario.
|
||
|
||
Recommendations:
|
||
|
||
* Use a **dedicated test customer** with:
|
||
|
||
* Preloaded credit.
|
||
* No penalty restrictions.
|
||
* Run E2E tests:
|
||
|
||
* On staging/pre-prod when possible.
|
||
* On production only as part of **controlled smoke** runs.
|
||
|
||
---
|
||
|
||
## 7. Summary
|
||
|
||
This test suite gives you:
|
||
|
||
* A **high-signal E2E validation** of the critical THR → MHD domestic flow.
|
||
* A set of **supporting tests** for IATA and rules configuration.
|
||
* A **presentable**, narrative output that clearly shows each phase:
|
||
|
||
* Authentication
|
||
* Discovery
|
||
* Booking
|
||
* Wallet validation
|
||
* Issuance
|
||
* Refund calculation
|
||
* Final refund state verification
|