versa 1.9.0

Versa types and utilities for developing Versa client applications in Rust
Documentation
use serde_json;
use std::fs;
use std::path::Path;

/// Load JSON fixture file
fn load_fixture(filename: &str) -> serde_json::Value {
  let path = Path::new(env!("CARGO_MANIFEST_DIR"))
    .join("tests/fixtures")
    .join(filename);
  let content =
    fs::read_to_string(path).expect(&format!("Failed to read fixture file: {}", filename));
  serde_json::from_str(&content).expect(&format!("Failed to parse JSON from fixture: {}", filename))
}

#[test]
fn test_flight_v1_11_0_full_example() {
  let json = load_fixture("flight_v1_11_0.json");

  // Deserialize into v1.11.0 receipt
  let receipt: versa::schema::v1_11_0::receipt::Receipt =
    serde_json::from_value(json.clone()).expect("Failed to deserialize v1.11.0 flight receipt");

  // Verify basic receipt structure
  assert_eq!(&*receipt.schema_version, "1.11.0");
  assert_eq!(receipt.header.total, 140220);
  assert_eq!(
    receipt.header.currency,
    versa::schema::v1_11_0::receipt::Currency::Usd
  );
  assert_eq!(
    receipt.header.invoice_number.as_ref().map(|s| s.as_str()),
    Some("1MzFN1K8F4fqH0lBmFq8CjbU")
  );

  // Verify flight data exists
  assert!(receipt.itemization.flight.is_some());
  let flight = receipt.itemization.flight.as_ref().unwrap();

  // Verify tickets
  assert_eq!(flight.tickets.len(), 2);

  // First ticket - Susy Smith (v1.11.0 uses string passenger)
  let ticket1 = &flight.tickets[0];
  assert_eq!(
    ticket1.passenger.as_ref().map(|s| s.as_str()),
    Some("Susy Smith")
  );
  assert_eq!(
    ticket1.number.as_ref().map(|s| s.as_str()),
    Some("0062698215636")
  );
  assert_eq!(
    ticket1.record_locator.as_ref().map(|s| s.as_str()),
    Some("CU9GEF")
  );
  assert_eq!(ticket1.segments.len(), 4);

  // Check first segment details
  let segment1 = &ticket1.segments[0];
  assert_eq!(&segment1.departure_airport_code, "SEA");
  assert_eq!(&segment1.arrival_airport_code, "MSP");
  assert_eq!(segment1.flight_number.as_deref(), Some("DL1768"));
  assert_eq!(segment1.seat.as_deref(), Some("15F"));
  assert_eq!(segment1.fare, Some(15233));

  // Verify taxes on first segment
  assert_eq!(segment1.taxes.len(), 4);
  assert_eq!(&segment1.taxes[0].name, "US Transportation Tax");
  assert_eq!(segment1.taxes[0].amount, 1142);

  // Check metadata on first ticket
  assert_eq!(ticket1.metadata.len(), 1);
  assert_eq!(ticket1.metadata[0].key, "AAdvantage #");
  assert_eq!(ticket1.metadata[0].value, "TH4700");

  // Second ticket - John Smith
  let ticket2 = &flight.tickets[1];
  assert_eq!(
    ticket2.passenger.as_ref().map(|s| s.as_str()),
    Some("John Smith")
  );
  assert_eq!(ticket2.metadata[0].value, "TH4703");

  // Verify payments
  assert_eq!(receipt.payments.len(), 1);
  let payment = &receipt.payments[0];
  assert_eq!(payment.amount, 140220);
  assert_eq!(
    payment.payment_type,
    Some(versa::schema::v1_11_0::receipt::PaymentPaymentType::Card)
  );

  // Verify card payment details
  assert!(payment.card_payment.is_some());
  let card = payment.card_payment.as_ref().unwrap();
  assert_eq!(&card.last_four, "4886");
  assert_eq!(
    card.network,
    Some(versa::schema::v1_11_0::receipt::CardPaymentNetwork::Mastercard)
  );

  // Test serialization round-trip
  let serialized = serde_json::to_value(&receipt).unwrap();
  let deserialized: versa::schema::v1_11_0::receipt::Receipt =
    serde_json::from_value(serialized).unwrap();
  assert_eq!(deserialized.header.total, receipt.header.total);
}

#[test]
fn test_flight_v2_0_0_full_example() {
  let json = load_fixture("flight_v2_0_0.json");

  // Deserialize into v2.0.0 receipt
  let receipt: versa::schema::v2_0_0::receipt::Receipt =
    serde_json::from_value(json.clone()).expect("Failed to deserialize v2.0.0 flight receipt");

  // Verify basic receipt structure
  assert_eq!(&*receipt.schema_version, "2.0.0");
  assert_eq!(receipt.header.total, 140220);
  assert_eq!(
    receipt.header.currency,
    versa::schema::v2_0_0::receipt::Currency::Usd
  );

  // Verify customer data (new in v2)
  assert!(receipt.header.customer.is_some());
  let customer = receipt.header.customer.as_ref().unwrap();
  assert_eq!(
    customer.website.as_ref().map(|s| s.as_str()),
    Some("example.com")
  );

  // Verify flight data exists
  assert!(receipt.itemization.flight.is_some());
  let flight = receipt.itemization.flight.as_ref().unwrap();

  // Verify tickets
  assert_eq!(flight.tickets.len(), 2);

  // First ticket - Susy Smith (v2.0.0 uses Person object)
  let ticket1 = &flight.tickets[0];
  assert!(ticket1.passenger.is_some());
  let passenger1 = ticket1.passenger.as_ref().unwrap();
  assert_eq!(
    passenger1.first_name.as_ref().map(|s| s.as_str()),
    Some("Susy")
  );
  assert_eq!(
    passenger1.last_name.as_ref().map(|s| s.as_str()),
    Some("Smith")
  );
  assert_eq!(
    passenger1.email.as_ref().map(|s| s.as_str()),
    Some("susy.smith@example.com")
  );
  assert_eq!(passenger1.preferred_first_name, None);
  assert_eq!(passenger1.phone, None);

  // Check Person metadata
  assert_eq!(passenger1.metadata.as_ref().unwrap().len(), 1);
  assert_eq!(passenger1.metadata.as_ref().unwrap()[0].key, "AAdvantage #");
  assert_eq!(passenger1.metadata.as_ref().unwrap()[0].value, "TH4700");

  // Second ticket - John Smith with Person object
  let ticket2 = &flight.tickets[1];
  assert!(ticket2.passenger.is_some());
  let passenger2 = ticket2.passenger.as_ref().unwrap();
  assert_eq!(
    passenger2.first_name.as_ref().map(|s| s.as_str()),
    Some("John")
  );
  assert_eq!(
    passenger2.last_name.as_ref().map(|s| s.as_str()),
    Some("Smith")
  );
  assert_eq!(
    passenger2.email.as_ref().map(|s| s.as_str()),
    Some("john.smith@example.com")
  );
  assert_eq!(passenger2.metadata.as_ref().unwrap()[0].value, "TH4703");

  // Verify segments are the same structure
  assert_eq!(ticket1.segments.len(), 4);
  let segment1 = &ticket1.segments[0];
  assert_eq!(&segment1.departure_airport_code.to_string(), "SEA");
  assert_eq!(&segment1.arrival_airport_code.to_string(), "MSP");

  // Test serialization round-trip
  let serialized = serde_json::to_value(&receipt).unwrap();
  let deserialized: versa::schema::v2_0_0::receipt::Receipt =
    serde_json::from_value(serialized).unwrap();
  assert_eq!(deserialized.header.total, receipt.header.total);
}

#[test]
fn test_passenger_field_comparison() {
  let v1_json = load_fixture("flight_v1_11_0.json");
  let v2_json = load_fixture("flight_v2_0_0.json");

  // Extract passenger data from v1.11.0
  let v1_passenger1 = &v1_json["itemization"]["flight"]["tickets"][0]["passenger"];
  let v1_passenger2 = &v1_json["itemization"]["flight"]["tickets"][1]["passenger"];

  assert_eq!(v1_passenger1.as_str().unwrap(), "Susy Smith");
  assert_eq!(v1_passenger2.as_str().unwrap(), "John Smith");

  // Extract passenger data from v2.0.0
  let v2_passenger1 = &v2_json["itemization"]["flight"]["tickets"][0]["passenger"];
  let v2_passenger2 = &v2_json["itemization"]["flight"]["tickets"][1]["passenger"];

  assert!(v2_passenger1.is_object());
  assert_eq!(v2_passenger1["first_name"].as_str().unwrap(), "Susy");
  assert_eq!(v2_passenger1["last_name"].as_str().unwrap(), "Smith");
  assert_eq!(
    v2_passenger1["email"].as_str().unwrap(),
    "susy.smith@example.com"
  );

  assert!(v2_passenger2.is_object());
  assert_eq!(v2_passenger2["first_name"].as_str().unwrap(), "John");
  assert_eq!(v2_passenger2["last_name"].as_str().unwrap(), "Smith");
  assert_eq!(
    v2_passenger2["email"].as_str().unwrap(),
    "john.smith@example.com"
  );

  // Both versions have metadata at the ticket level
  let v1_metadata1 = &v1_json["itemization"]["flight"]["tickets"][0]["metadata"];
  let v2_metadata1 = &v2_json["itemization"]["flight"]["tickets"][0]["passenger"]["metadata"];

  assert_eq!(v1_metadata1[0]["key"].as_str().unwrap(), "AAdvantage #");
  assert_eq!(v2_metadata1[0]["key"].as_str().unwrap(), "AAdvantage #");
}

#[test]
fn test_schema_compatibility() {
  // Test that we can detect version and route appropriately
  let v1_json = load_fixture("flight_v1_11_0.json");
  let v2_json = load_fixture("flight_v2_0_0.json");

  let v1_version = v1_json["schema_version"].as_str().unwrap();
  let v2_version = v2_json["schema_version"].as_str().unwrap();

  assert_eq!(v1_version, "1.11.0");
  assert_eq!(v2_version, "2.0.0");

  // Ensure v1 can't be deserialized as v2 (different passenger structure)
  let v1_as_v2_result: Result<versa::schema::v2_0_0::receipt::Receipt, _> =
    serde_json::from_value(v1_json.clone());
  assert!(
    v1_as_v2_result.is_err(),
    "v1.11.0 should not deserialize as v2.0.0 due to passenger field difference"
  );

  // Ensure v2 can't be deserialized as v1 (different passenger structure)
  let v2_as_v1_result: Result<versa::schema::v1_11_0::receipt::Receipt, _> =
    serde_json::from_value(v2_json.clone());
  assert!(
    v2_as_v1_result.is_err(),
    "v2.0.0 should not deserialize as v1.11.0 due to passenger field difference"
  );
}

#[test]
fn test_tax_calculation() {
  let v1_json = load_fixture("flight_v1_11_0.json");
  let v2_json = load_fixture("flight_v2_0_0.json");

  // Both versions should have the same tax structure
  let v1_receipt: versa::schema::v1_11_0::receipt::Receipt =
    serde_json::from_value(v1_json).unwrap();
  let v2_receipt: versa::schema::v2_0_0::receipt::Receipt =
    serde_json::from_value(v2_json).unwrap();

  let v1_flight = v1_receipt.itemization.flight.as_ref().unwrap();
  let v2_flight = v2_receipt.itemization.flight.as_ref().unwrap();

  // Calculate total taxes for first ticket, first segment
  let v1_taxes = &v1_flight.tickets[0].segments[0].taxes;
  let v2_taxes = &v2_flight.tickets[0].segments[0].taxes;

  let v1_total_tax: i64 = v1_taxes.iter().map(|t| t.amount).sum();
  let v2_total_tax: i64 = v2_taxes.as_ref().unwrap().iter().map(|t| t.amount).sum();

  assert_eq!(v1_total_tax, v2_total_tax);
  assert_eq!(v1_total_tax, 2272); // 1142 + 280 + 450 + 400
}

#[test]
fn test_payment_details() {
  let v1_json = load_fixture("flight_v1_11_0.json");
  let v2_json = load_fixture("flight_v2_0_0.json");

  let v1_receipt: versa::schema::v1_11_0::receipt::Receipt =
    serde_json::from_value(v1_json).unwrap();
  let v2_receipt: versa::schema::v2_0_0::receipt::Receipt =
    serde_json::from_value(v2_json).unwrap();

  // Both should have same payment structure
  assert_eq!(v1_receipt.payments.len(), 1);
  assert_eq!(v2_receipt.payments.len(), 1);

  let v1_payment = &v1_receipt.payments[0];
  let v2_payment = &v2_receipt.payments[0];

  assert_eq!(v1_payment.amount, v2_payment.amount);
  assert_eq!(v1_payment.amount, 140220);

  // Check card details
  let v1_card = v1_payment.card_payment.as_ref().unwrap();
  let v2_card = v2_payment.card_payment.as_ref().unwrap();

  assert_eq!(v1_card.last_four, v2_card.last_four.to_string());
  assert_eq!(
    v1_card.network,
    Some(versa::schema::v1_11_0::receipt::CardPaymentNetwork::Mastercard)
  );
  assert_eq!(
    v2_card.network,
    Some(versa::schema::v2_0_0::receipt::CardPaymentNetwork::Mastercard)
  );
}