Files
avaair-e2e-tests/README.md
Reza Esmaeili 9e69b14249 Init commit
2025-12-04 12:46:23 +03:30

549 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AvaAir E2E Flight Flow Test Suite
This test suite exercises the **end-to-end customer journey** for AvaAirs 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 users 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 projects `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 Pytests “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 accounts 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