12 KiB
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.pyGlobal 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_waitpacing helper for stabilizing async flows.
-
test_flight_thr_mhd_e2e.pyActual 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
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
TEST_MOBILE_COUNTRY_CODE=98
TEST_MOBILE_NUMBER=9014332990
TEST_OTP_CODE=<optional pre-shared OTP>
TEST_MOBILE_COUNTRY_CODEandTEST_MOBILE_NUMBERdefine the test customer account used for OTP login.TEST_OTP_CODEcontrols 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:
➡️ 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
TEST_BOOK_EMAIL=iamrezazoom@gmail.com
TEST_BOOK_PHONE=9014332990
- Email and phone number used in
/customer/flight/bookpayloads. - 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:
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.
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:
@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):
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:
-
Sends OTP request:
POST /users/otp/request -
Resolves OTP from:
TEST_OTP_CODE(if set), or- interactive
input()prompt.
-
Validates OTP via:
POST /users/otp/check -
Asserts the response contains:
auth_tokenrefresh_tokenuser
-
Returns an auth context dict:
{ "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:
@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:
@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 + 7days. - Always sort by
lowest-priceto 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.
datais a non-empty list.- Both
THRandMHDare 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.
datais a non-empty list.THRappears 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 = falseother_iata_code = "THR"
-
Validations:
- HTTP 200.
datais a non-empty list.MHDappears.THRdoes 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-rulesGET /customer/setting/get/refund-rules
Validations:
-
general-rules:data.general-rulesexists and is non-empty.
-
refund-rules:data.refund_rules.domestic.datais a non-empty list of objects.data.refund_rules.international.datathe same.- First item in each has
descriptionandpenalty.
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:
-
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
THRtoMHD, total_fare > 0.
- valid
-
-
-
Book the first itinerary
-
POST /customer/flight/book -
Payload includes:
- Contact info (
email,phone_number). - Single ADT traveler (driven by env / defaults).
- Contact info (
-
Validates:
- HTTP 200.
id(booking_id) is positive.expires_at > 0.price > 0(booking_price).
-
-
Verify wallet credit
-
GET /admin/user/credit -
Validates:
- HTTP 200.
creditis numeric.credit >= booking_price.
-
This step guarantees the issue step using wallet credit will succeed and is not blocked by insufficient balance.
-
-
Issue the ticket using wallet credit
-
POST /customer/flight/book/{booking_id}/issue -
Payload:
pay_with_credit = truepay_for = "ticket"platform = "mobile"
-
Validates:
- HTTP 204 (No Content).
-
Followed by
api_wait(2.0)to allow backend to finalize issuance.
-
-
Fetch active tickets and locate the issued one
-
GET /customer/ticketwith:is_past = falsestatuses = confirmed
-
Validates:
- HTTP 200.
- Non-empty ticket list.
- Ticket with
total == booking_priceexists.
-
Extracts:
ticket_idticket_traveler_id(first traveler)ticket_segment_id(first segment)
-
-
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
dataarray. -
First item:
payable > 0amount_that_should_pay_back_to_passenger > 0penalty_percent == 0amount_that_should_pay_back_to_passenger == booking_priceticket_traveler_idmatches the one sent.
-
-
-
Perform refund
-
POST /customer/flight/refund -
Payload:
- same
ticket_segment_idandticket_traveler_id.
- same
-
Validates:
- HTTP 200.
-
Followed by
api_wait(2.0)to allow status propagation.
-
-
Validate post-refund ticket state
-
GET /customer/ticketwith:is_past = truestatuses = confirmed,refunded_partially,refunded
-
Locates the same
ticket_id. -
Validates:
refund_statusis 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
pip install pytest requests
(Or include them in your project’s requirements.txt / poetry / pipenv.)
5.2 Run all tests
pytest -q -s
-
-skeeps 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:
pytest -q -s -m e2e
To avoid Pytest’s “unknown mark” warning, add a pytest.ini in the repo root:
# 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