# 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= ``` * `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