Files
alpinebits_python/tests/helpers/xml_builders.py
2025-12-01 11:12:22 +01:00

393 lines
13 KiB
Python

"""XML builder helpers for creating test reservation data.
This module provides convenient builder classes for generating reservation XML
structures used in conversion service tests.
"""
from datetime import datetime, timedelta
from typing import Optional
from xml.etree import ElementTree as ET
class RoomReservationBuilder:
"""Builder for creating roomReservation XML elements with daily sales."""
def __init__(
self,
arrival: str,
departure: str,
room_type: str = "DZV",
room_number: str = "101",
status: str = "reserved",
adults: int = 2,
children: int = 0,
infants: int = 0,
rate_plan_code: str = "STANDARD",
connected_room_type: str = "0",
revenue_logis_per_day: Optional[float] = None,
revenue_total_per_day: Optional[float] = None,
):
"""Initialize room reservation builder.
Args:
arrival: Arrival date in YYYY-MM-DD format
departure: Departure date in YYYY-MM-DD format
room_type: Room type code
room_number: Room number
status: Reservation status (reserved, request, confirmed, etc.)
adults: Number of adults
children: Number of children
infants: Number of infants
rate_plan_code: Rate plan code
connected_room_type: Connected room type code
revenue_logis_per_day: Revenue per day (if None, no revenue attributes)
revenue_total_per_day: Total revenue per day (defaults to revenue_logis_per_day)
"""
self.arrival = arrival
self.departure = departure
self.room_type = room_type
self.room_number = room_number
self.status = status
self.adults = adults
self.children = children
self.infants = infants
self.rate_plan_code = rate_plan_code
self.connected_room_type = connected_room_type
self.revenue_logis_per_day = revenue_logis_per_day
self.revenue_total_per_day = revenue_total_per_day or revenue_logis_per_day
def build(self) -> ET.Element:
"""Build the roomReservation XML element with daily sales.
Returns:
XML Element for the room reservation
"""
room_attrs = {
"arrival": self.arrival,
"departure": self.departure,
"status": self.status,
"roomType": self.room_type,
"roomNumber": self.room_number,
"adults": str(self.adults),
"ratePlanCode": self.rate_plan_code,
"connectedRoomType": self.connected_room_type,
}
if self.children > 0:
room_attrs["children"] = str(self.children)
if self.infants > 0:
room_attrs["infants"] = str(self.infants)
room_elem = ET.Element("roomReservation", room_attrs)
# Create dailySales element
daily_sales_elem = ET.SubElement(room_elem, "dailySales")
# Generate daily sale entries from arrival to departure (inclusive of departure for the no-revenue entry)
arrival_date = datetime.strptime(self.arrival, "%Y-%m-%d")
departure_date = datetime.strptime(self.departure, "%Y-%m-%d")
current_date = arrival_date
while current_date <= departure_date:
date_str = current_date.strftime("%Y-%m-%d")
daily_sale_attrs = {"date": date_str}
# Add revenue attributes for all days except departure day
if current_date < departure_date and self.revenue_logis_per_day is not None:
daily_sale_attrs["revenueTotal"] = str(self.revenue_total_per_day)
daily_sale_attrs["revenueLogis"] = str(self.revenue_logis_per_day)
ET.SubElement(daily_sales_elem, "dailySale", daily_sale_attrs)
current_date += timedelta(days=1)
return room_elem
class ReservationXMLBuilder:
"""Builder for creating complete reservation XML structures for testing.
This builder provides a fluent interface for constructing reservation XML
that matches the format expected by the ConversionService.
Example usage:
builder = ReservationXMLBuilder(
hotel_id="39054_001",
reservation_id="12345",
reservation_number="RES-001",
reservation_date="2025-11-14"
)
builder.set_guest(
guest_id="guest_001",
first_name="John",
last_name="Doe",
email="john@example.com"
)
builder.add_room(
arrival="2025-12-01",
departure="2025-12-05",
revenue_logis_per_day=150.0
)
xml_string = builder.build_xml()
"""
def __init__(
self,
hotel_id: str,
reservation_id: str,
reservation_number: str,
reservation_date: str,
creation_time: Optional[str] = None,
reservation_type: str = "reservation",
advertising_medium: Optional[str] = None,
advertising_partner: Optional[str] = None,
advertising_campagne: Optional[str] = None,
):
"""Initialize reservation builder.
Args:
hotel_id: Hotel ID
reservation_id: Reservation ID
reservation_number: Reservation number
reservation_date: Reservation date in YYYY-MM-DD format
creation_time: Creation timestamp (defaults to reservation_date + T00:00:00)
reservation_type: Type of reservation (reservation, request, etc.)
advertising_medium: Advertising medium
advertising_partner: Advertising partner
advertising_campagne: Advertising campaign
"""
self.hotel_id = hotel_id
self.reservation_id = reservation_id
self.reservation_number = reservation_number
self.reservation_date = reservation_date
self.creation_time = creation_time or f"{reservation_date}T00:00:00"
self.reservation_type = reservation_type
self.advertising_medium = advertising_medium
self.advertising_partner = advertising_partner
self.advertising_campagne = advertising_campagne
self.guest_data: Optional[dict] = None
self.rooms: list[RoomReservationBuilder] = []
def set_guest(
self,
guest_id: str,
first_name: str,
last_name: str,
email: str,
language: str = "en",
gender: Optional[str] = None,
country_code: Optional[str] = None,
country: Optional[str] = None,
) -> "ReservationXMLBuilder":
"""Set guest information for the reservation.
Args:
guest_id: Guest ID
first_name: Guest first name
last_name: Guest last name
email: Guest email
language: Guest language code
gender: Guest gender
country_code: Guest country code
country: Guest country name
Returns:
Self for method chaining
"""
self.guest_data = {
"id": guest_id,
"firstName": first_name,
"lastName": last_name,
"email": email,
"language": language,
}
if gender:
self.guest_data["gender"] = gender
if country_code:
self.guest_data["countryCode"] = country_code
if country:
self.guest_data["country"] = country
return self
def add_room(
self,
arrival: str,
departure: str,
room_type: str = "DZV",
room_number: str = "101",
status: str = "reserved",
adults: int = 2,
children: int = 0,
infants: int = 0,
rate_plan_code: str = "STANDARD",
connected_room_type: str = "0",
revenue_logis_per_day: Optional[float] = None,
revenue_total_per_day: Optional[float] = None,
) -> "ReservationXMLBuilder":
"""Add a room reservation with convenient daily sales generation.
Args:
arrival: Arrival date in YYYY-MM-DD format
departure: Departure date in YYYY-MM-DD format
room_type: Room type code
room_number: Room number
status: Reservation status
adults: Number of adults
children: Number of children
infants: Number of infants
rate_plan_code: Rate plan code
connected_room_type: Connected room type
revenue_logis_per_day: Fixed revenue per day (auto-generates dailySale entries)
revenue_total_per_day: Total revenue per day (defaults to revenue_logis_per_day)
Returns:
Self for method chaining
"""
room_builder = RoomReservationBuilder(
arrival=arrival,
departure=departure,
room_type=room_type,
room_number=room_number,
status=status,
adults=adults,
children=children,
infants=infants,
rate_plan_code=rate_plan_code,
connected_room_type=connected_room_type,
revenue_logis_per_day=revenue_logis_per_day,
revenue_total_per_day=revenue_total_per_day,
)
self.rooms.append(room_builder)
return self
def add_room_builder(
self, room_builder: RoomReservationBuilder
) -> "ReservationXMLBuilder":
"""Add a pre-configured room builder.
Args:
room_builder: RoomReservationBuilder instance
Returns:
Self for method chaining
"""
self.rooms.append(room_builder)
return self
def build(self) -> ET.Element:
"""Build the reservation XML element.
Returns:
XML Element for the reservation
"""
reservation_attrs = {
"hotelID": self.hotel_id,
"id": self.reservation_id,
"number": self.reservation_number,
"date": self.reservation_date,
"creationTime": self.creation_time,
"type": self.reservation_type,
}
if self.advertising_medium:
reservation_attrs["advertisingMedium"] = self.advertising_medium
if self.advertising_partner:
reservation_attrs["advertisingPartner"] = self.advertising_partner
if self.advertising_campagne:
reservation_attrs["advertisingCampagne"] = self.advertising_campagne
reservation_elem = ET.Element("reservation", reservation_attrs)
# Add guest element
if self.guest_data:
ET.SubElement(reservation_elem, "guest", self.guest_data)
# Add roomReservations
if self.rooms:
room_reservations_elem = ET.SubElement(
reservation_elem, "roomReservations"
)
for room_builder in self.rooms:
room_elem = room_builder.build()
room_reservations_elem.append(room_elem)
return reservation_elem
def build_xml(self, include_xml_declaration: bool = True) -> str:
"""Build the complete XML string for this reservation.
Args:
include_xml_declaration: Whether to include <?xml version="1.0"?> declaration
Returns:
XML string
"""
reservation_elem = self.build()
# Wrap in <reservations> root element
root = ET.Element("reservations")
root.append(reservation_elem)
xml_str = ET.tostring(root, encoding="unicode")
if include_xml_declaration:
xml_str = '<?xml version="1.0" ?>\n' + xml_str
return xml_str
class MultiReservationXMLBuilder:
"""Builder for creating XML documents with multiple reservations.
Example:
builder = MultiReservationXMLBuilder()
builder.add_reservation(
ReservationXMLBuilder(...).set_guest(...).add_room(...)
)
builder.add_reservation(
ReservationXMLBuilder(...).set_guest(...).add_room(...)
)
xml_string = builder.build_xml()
"""
def __init__(self):
"""Initialize multi-reservation builder."""
self.reservations: list[ReservationXMLBuilder] = []
def add_reservation(
self, reservation_builder: ReservationXMLBuilder
) -> "MultiReservationXMLBuilder":
"""Add a reservation to the document.
Args:
reservation_builder: ReservationXMLBuilder instance
Returns:
Self for method chaining
"""
self.reservations.append(reservation_builder)
return self
def build_xml(self, include_xml_declaration: bool = True) -> str:
"""Build the complete XML string with all reservations.
Args:
include_xml_declaration: Whether to include <?xml version="1.0"?> declaration
Returns:
XML string with multiple reservations
"""
root = ET.Element("reservations")
for reservation_builder in self.reservations:
reservation_elem = reservation_builder.build()
root.append(reservation_elem)
xml_str = ET.tostring(root, encoding="unicode")
if include_xml_declaration:
xml_str = '<?xml version="1.0" ?>\n' + xml_str
return xml_str