Init commit
This commit is contained in:
548
README.md
Normal file
548
README.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user