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

12 KiB
Raw Permalink Blame History

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

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_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:

    ➡️  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/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:

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:

  1. Sends OTP request:

    POST /users/otp/request
    
  2. Resolves OTP from:

    • TEST_OTP_CODE (if set), or
    • interactive input() prompt.
  3. Validates OTP via:

    POST /users/otp/check
    
  4. Asserts the response contains:

    • auth_token
    • refresh_token
    • user
  5. 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 + 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

pip install pytest requests

(Or include them in your projects requirements.txt / poetry / pipenv.)


5.2 Run all tests

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:

pytest -q -s -m e2e

To avoid Pytests “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 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