Fixed up the damm tests
This commit is contained in:
@@ -21,7 +21,7 @@ from .db import (
|
|||||||
SessionMaker,
|
SessionMaker,
|
||||||
)
|
)
|
||||||
from .logging_config import get_logger
|
from .logging_config import get_logger
|
||||||
from .schemas import ConversionGuestData
|
from .schemas import ConversionData, ConversionGuestData
|
||||||
|
|
||||||
_LOGGER = get_logger(__name__)
|
_LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
@@ -552,7 +552,7 @@ class ConversionService:
|
|||||||
pms_reservation_id if successfully created/updated, None if error occurred
|
pms_reservation_id if successfully created/updated, None if error occurred
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pms_reservation_id = reservation_elem.get("id")
|
pms_reservation_id = int(reservation_elem.get("id"))
|
||||||
|
|
||||||
async with semaphore:
|
async with semaphore:
|
||||||
# In concurrent mode, create a new session for this task
|
# In concurrent mode, create a new session for this task
|
||||||
@@ -652,9 +652,15 @@ class ConversionService:
|
|||||||
"daily_sales_count": 0,
|
"daily_sales_count": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract reservation metadata
|
|
||||||
hotel_id = reservation_elem.get("hotelID")
|
hotel_id = reservation_elem.get("hotelID")
|
||||||
pms_reservation_id = reservation_elem.get("id")
|
try:
|
||||||
|
# Extract reservation metadata
|
||||||
|
|
||||||
|
pms_reservation_id = int(reservation_elem.get("id"))
|
||||||
|
except ValueError as e:
|
||||||
|
_LOGGER.error("Invalid reservation metadata in reservation element: %s", e)
|
||||||
|
return stats
|
||||||
|
|
||||||
reservation_number = reservation_elem.get("number")
|
reservation_number = reservation_elem.get("number")
|
||||||
reservation_date_str = reservation_elem.get("date")
|
reservation_date_str = reservation_elem.get("date")
|
||||||
creation_time_str = reservation_elem.get("creationTime")
|
creation_time_str = reservation_elem.get("creationTime")
|
||||||
@@ -751,11 +757,8 @@ class ConversionService:
|
|||||||
else:
|
else:
|
||||||
# Create new conversion entry (without matching - will be done later)
|
# Create new conversion entry (without matching - will be done later)
|
||||||
# Note: Guest information (first_name, last_name, email, etc) is stored in ConversionGuest table
|
# Note: Guest information (first_name, last_name, email, etc) is stored in ConversionGuest table
|
||||||
conversion = Conversion(
|
conversion_data = ConversionData(
|
||||||
# Links to existing entities (nullable, will be filled in after matching)
|
# Links to existing entities (nullable, will be filled in after matching)
|
||||||
reservation_id=None,
|
|
||||||
customer_id=None,
|
|
||||||
hashed_customer_id=None,
|
|
||||||
# Reservation metadata
|
# Reservation metadata
|
||||||
hotel_id=hotel_id,
|
hotel_id=hotel_id,
|
||||||
guest_id=guest_id, # Links to ConversionGuest
|
guest_id=guest_id, # Links to ConversionGuest
|
||||||
@@ -770,9 +773,8 @@ class ConversionService:
|
|||||||
advertising_partner=advertising_partner,
|
advertising_partner=advertising_partner,
|
||||||
advertising_campagne=advertising_campagne,
|
advertising_campagne=advertising_campagne,
|
||||||
# Metadata
|
# Metadata
|
||||||
created_at=datetime.now(),
|
|
||||||
updated_at=datetime.now(),
|
|
||||||
)
|
)
|
||||||
|
conversion = Conversion(**conversion_data.model_dump())
|
||||||
session.add(conversion)
|
session.add(conversion)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Created conversion (pms_id=%s)",
|
"Created conversion (pms_id=%s)",
|
||||||
@@ -1503,7 +1505,7 @@ class ConversionService:
|
|||||||
|
|
||||||
async def _match_conversion_from_db_safe(
|
async def _match_conversion_from_db_safe(
|
||||||
self,
|
self,
|
||||||
pms_reservation_id: str,
|
pms_reservation_id: int,
|
||||||
semaphore: asyncio.Semaphore,
|
semaphore: asyncio.Semaphore,
|
||||||
stats: dict[str, int],
|
stats: dict[str, int],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from XML generation (xsdata) follows clean architecture principles.
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from datetime import date, datetime
|
from datetime import UTC, date, datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -20,6 +20,35 @@ from pydantic import BaseModel, EmailStr, Field, field_validator, model_validato
|
|||||||
from .const import WebhookStatus
|
from .const import WebhookStatus
|
||||||
|
|
||||||
|
|
||||||
|
# Generalized integer validator for reuse across models
|
||||||
|
def convert_to_int(field_name: str, v: Any) -> int:
|
||||||
|
"""Convert a value to integer, handling string inputs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field_name: Name of the field being validated (for error messages)
|
||||||
|
v: Value to convert (can be int, str, or None)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Integer value
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If value is None or cannot be converted to int
|
||||||
|
|
||||||
|
"""
|
||||||
|
if v is None:
|
||||||
|
msg = f"{field_name} cannot be None"
|
||||||
|
raise ValueError(msg)
|
||||||
|
if isinstance(v, int):
|
||||||
|
return v
|
||||||
|
if isinstance(v, str):
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except ValueError as e:
|
||||||
|
msg = f"{field_name} must be a valid integer, got: {v}"
|
||||||
|
raise ValueError(msg) from e
|
||||||
|
msg = f"{field_name} must be int or str, got: {type(v)}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Country name to ISO 3166-1 alpha-2 code mapping
|
# Country name to ISO 3166-1 alpha-2 code mapping
|
||||||
COUNTRY_NAME_TO_CODE = {
|
COUNTRY_NAME_TO_CODE = {
|
||||||
# English names
|
# English names
|
||||||
@@ -195,6 +224,7 @@ class CustomerData(BaseModel):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
2-letter ISO country code (uppercase) or None if input is None/empty
|
2-letter ISO country code (uppercase) or None if input is None/empty
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not v:
|
if not v:
|
||||||
return None
|
return None
|
||||||
@@ -367,8 +397,7 @@ class WebhookRequestData(BaseModel):
|
|||||||
|
|
||||||
# Required fields
|
# Required fields
|
||||||
payload_json: dict[str, Any] | None = Field(
|
payload_json: dict[str, Any] | None = Field(
|
||||||
...,
|
..., description="Webhook payload (required for creation, nullable after purge)"
|
||||||
description="Webhook payload (required for creation, nullable after purge)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Auto-calculated from payload_json
|
# Auto-calculated from payload_json
|
||||||
@@ -376,7 +405,7 @@ class WebhookRequestData(BaseModel):
|
|||||||
None,
|
None,
|
||||||
min_length=64,
|
min_length=64,
|
||||||
max_length=64,
|
max_length=64,
|
||||||
description="SHA256 hash of canonical JSON payload (auto-calculated)"
|
description="SHA256 hash of canonical JSON payload (auto-calculated)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Optional foreign keys
|
# Optional foreign keys
|
||||||
@@ -517,49 +546,71 @@ class ConversionGuestData(BaseModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def convert_guest_id_to_int(cls, v: Any) -> int:
|
def convert_guest_id_to_int(cls, v: Any) -> int:
|
||||||
"""Convert guest_id to integer (handles string input from XML)."""
|
"""Convert guest_id to integer (handles string input from XML)."""
|
||||||
if v is None:
|
return convert_to_int("guest_id", v)
|
||||||
raise ValueError("guest_id cannot be None")
|
|
||||||
if isinstance(v, int):
|
|
||||||
return v
|
|
||||||
if isinstance(v, str):
|
|
||||||
try:
|
|
||||||
return int(v)
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValueError(f"guest_id must be a valid integer, got: {v}") from e
|
|
||||||
raise ValueError(f"guest_id must be int or str, got: {type(v)}")
|
|
||||||
|
|
||||||
model_config = {"from_attributes": True}
|
model_config = {"from_attributes": True}
|
||||||
|
|
||||||
|
|
||||||
class ReservationService:
|
class ConversionData(BaseModel):
|
||||||
"""Example service showing how to use Pydantic models with SQLAlchemy."""
|
"""Validated conversion data from PMS XML.
|
||||||
|
|
||||||
def __init__(self, db_session):
|
Handles validation for conversion records extracted from
|
||||||
self.db_session = db_session
|
hotel PMS conversion XML files. This model ensures proper type conversion
|
||||||
|
and validation before creating a Conversion database entry.
|
||||||
async def create_reservation(
|
|
||||||
self, reservation_data: ReservationData, customer_data: CustomerData
|
|
||||||
):
|
|
||||||
"""Create a reservation with validated data.
|
|
||||||
|
|
||||||
The data has already been validated by Pydantic before reaching here.
|
|
||||||
"""
|
"""
|
||||||
from alpine_bits_python.db import Customer, Reservation
|
|
||||||
|
|
||||||
# Convert validated Pydantic model to SQLAlchemy model
|
# Foreign key references (nullable - matched after creation)
|
||||||
db_customer = Customer(**customer_data.model_dump(exclude_none=True))
|
reservation_id: int | None = Field(None, gt=0)
|
||||||
self.db_session.add(db_customer)
|
customer_id: int | None = Field(None, gt=0)
|
||||||
await self.db_session.flush() # Get the customer ID
|
hashed_customer_id: int | None = Field(None, gt=0)
|
||||||
|
|
||||||
# Create reservation linked to customer
|
# Required reservation metadata from PMS
|
||||||
db_reservation = Reservation(
|
hotel_id: str = Field(..., min_length=1, max_length=50)
|
||||||
customer_id=db_customer.id,
|
pms_reservation_id: int = Field(..., gt=0)
|
||||||
**reservation_data.model_dump(
|
guest_id: int | None = Field(None, gt=0)
|
||||||
exclude={"children_ages"}
|
|
||||||
), # Handle separately
|
# Optional reservation metadata
|
||||||
children_ages=",".join(map(str, reservation_data.children_ages)),
|
reservation_number: str | None = Field(None, max_length=100)
|
||||||
|
reservation_date: date | None = None
|
||||||
|
creation_time: datetime | None = None
|
||||||
|
reservation_type: str | None = Field(None, max_length=50)
|
||||||
|
booking_channel: str | None = Field(None, max_length=100)
|
||||||
|
|
||||||
|
# Advertising/tracking data (used for matching)
|
||||||
|
advertising_medium: str | None = Field(None, max_length=200)
|
||||||
|
advertising_partner: str | None = Field(None, max_length=200)
|
||||||
|
advertising_campagne: str | None = Field(None, max_length=500)
|
||||||
|
|
||||||
|
# Attribution flags
|
||||||
|
directly_attributable: bool = Field(default=False)
|
||||||
|
guest_matched: bool = Field(default=False)
|
||||||
|
|
||||||
|
# Timestamps (auto-managed)
|
||||||
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||||
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||||
|
|
||||||
|
@field_validator(
|
||||||
|
"pms_reservation_id", "guest_id", "reservation_id", "customer_id",
|
||||||
|
"hashed_customer_id", mode="before"
|
||||||
)
|
)
|
||||||
self.db_session.add(db_reservation)
|
@classmethod
|
||||||
await self.db_session.commit()
|
def convert_int_fields(cls, v: Any) -> int | None:
|
||||||
|
"""Convert integer fields from string to int (handles XML input)."""
|
||||||
|
if v is None or v == "":
|
||||||
|
return None
|
||||||
|
# Get the field name from the validation context if available
|
||||||
|
# For now, use a generic name since we handle multiple fields
|
||||||
|
return convert_to_int("field", v)
|
||||||
|
|
||||||
return db_reservation, db_customer
|
@field_validator("hotel_id", "reservation_number", "reservation_type",
|
||||||
|
"booking_channel", "advertising_medium", "advertising_partner",
|
||||||
|
"advertising_campagne", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def strip_string_fields(cls, v: str | None) -> str | None:
|
||||||
|
"""Strip whitespace from string fields."""
|
||||||
|
if v is None:
|
||||||
|
return None
|
||||||
|
stripped = str(v).strip()
|
||||||
|
return stripped if stripped else None
|
||||||
|
|
||||||
|
model_config = {"from_attributes": True}
|
||||||
|
|||||||
@@ -9,6 +9,43 @@ from typing import Optional
|
|||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
def validate_and_convert_id(field_name: str, value: str | int) -> str:
|
||||||
|
"""Validate that an ID field is convertible to integer and return as string.
|
||||||
|
|
||||||
|
This helper ensures ID fields (like reservation_id, guest_id) are valid integers,
|
||||||
|
which is important since the Pydantic models will convert them from strings to ints.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field_name: Name of the field for error messages
|
||||||
|
value: The ID value (can be string or int)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String representation of the validated integer ID
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If value cannot be converted to a valid positive integer
|
||||||
|
|
||||||
|
"""
|
||||||
|
def _raise_invalid_type_error():
|
||||||
|
"""Raise error for invalid ID type."""
|
||||||
|
msg = (
|
||||||
|
f"{field_name} must be convertible to a positive integer, "
|
||||||
|
f"got: {value!r} (type: {type(value).__name__})"
|
||||||
|
)
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Convert to int first to validate it's a valid integer
|
||||||
|
int_value = int(value)
|
||||||
|
if int_value <= 0:
|
||||||
|
msg = f"{field_name} must be a positive integer, got: {value}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
# Return as string for XML attributes
|
||||||
|
return str(int_value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
_raise_invalid_type_error()
|
||||||
|
|
||||||
|
|
||||||
class RoomReservationBuilder:
|
class RoomReservationBuilder:
|
||||||
"""Builder for creating roomReservation XML elements with daily sales."""
|
"""Builder for creating roomReservation XML elements with daily sales."""
|
||||||
|
|
||||||
@@ -133,7 +170,7 @@ class ReservationXMLBuilder:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hotel_id: str,
|
hotel_id: str,
|
||||||
reservation_id: str,
|
reservation_id: str | int,
|
||||||
reservation_number: str,
|
reservation_number: str,
|
||||||
reservation_date: str,
|
reservation_date: str,
|
||||||
creation_time: Optional[str] = None,
|
creation_time: Optional[str] = None,
|
||||||
@@ -146,7 +183,7 @@ class ReservationXMLBuilder:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
hotel_id: Hotel ID
|
hotel_id: Hotel ID
|
||||||
reservation_id: Reservation ID
|
reservation_id: Reservation ID (must be convertible to positive integer)
|
||||||
reservation_number: Reservation number
|
reservation_number: Reservation number
|
||||||
reservation_date: Reservation date in YYYY-MM-DD format
|
reservation_date: Reservation date in YYYY-MM-DD format
|
||||||
creation_time: Creation timestamp (defaults to reservation_date + T00:00:00)
|
creation_time: Creation timestamp (defaults to reservation_date + T00:00:00)
|
||||||
@@ -156,7 +193,7 @@ class ReservationXMLBuilder:
|
|||||||
advertising_campagne: Advertising campaign
|
advertising_campagne: Advertising campaign
|
||||||
"""
|
"""
|
||||||
self.hotel_id = hotel_id
|
self.hotel_id = hotel_id
|
||||||
self.reservation_id = reservation_id
|
self.reservation_id = validate_and_convert_id("reservation_id", reservation_id)
|
||||||
self.reservation_number = reservation_number
|
self.reservation_number = reservation_number
|
||||||
self.reservation_date = reservation_date
|
self.reservation_date = reservation_date
|
||||||
self.creation_time = creation_time or f"{reservation_date}T00:00:00"
|
self.creation_time = creation_time or f"{reservation_date}T00:00:00"
|
||||||
@@ -170,7 +207,7 @@ class ReservationXMLBuilder:
|
|||||||
|
|
||||||
def set_guest(
|
def set_guest(
|
||||||
self,
|
self,
|
||||||
guest_id: str,
|
guest_id: str | int,
|
||||||
first_name: str,
|
first_name: str,
|
||||||
last_name: str,
|
last_name: str,
|
||||||
email: str,
|
email: str,
|
||||||
@@ -182,7 +219,7 @@ class ReservationXMLBuilder:
|
|||||||
"""Set guest information for the reservation.
|
"""Set guest information for the reservation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
guest_id: Guest ID
|
guest_id: Guest ID (must be convertible to positive integer)
|
||||||
first_name: Guest first name
|
first_name: Guest first name
|
||||||
last_name: Guest last name
|
last_name: Guest last name
|
||||||
email: Guest email
|
email: Guest email
|
||||||
@@ -194,8 +231,9 @@ class ReservationXMLBuilder:
|
|||||||
Returns:
|
Returns:
|
||||||
Self for method chaining
|
Self for method chaining
|
||||||
"""
|
"""
|
||||||
|
validated_guest_id = validate_and_convert_id("guest_id", guest_id)
|
||||||
self.guest_data = {
|
self.guest_data = {
|
||||||
"id": guest_id,
|
"id": validated_guest_id,
|
||||||
"firstName": first_name,
|
"firstName": first_name,
|
||||||
"lastName": last_name,
|
"lastName": last_name,
|
||||||
"email": email,
|
"email": email,
|
||||||
|
|||||||
@@ -372,13 +372,13 @@ class TestConversionServiceWithImportedData:
|
|||||||
res1_v1 = (
|
res1_v1 = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="res_001",
|
reservation_id="100",
|
||||||
reservation_number="RES-001",
|
reservation_number="100",
|
||||||
reservation_date="2025-11-14",
|
reservation_date="2025-11-14",
|
||||||
reservation_type="request",
|
reservation_type="request",
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_001",
|
guest_id="100",
|
||||||
first_name="Alice",
|
first_name="Alice",
|
||||||
last_name="Johnson",
|
last_name="Johnson",
|
||||||
email="alice@example.com",
|
email="alice@example.com",
|
||||||
@@ -397,13 +397,13 @@ class TestConversionServiceWithImportedData:
|
|||||||
res2_v1 = (
|
res2_v1 = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="res_002",
|
reservation_id="101",
|
||||||
reservation_number="RES-002",
|
reservation_number="101",
|
||||||
reservation_date="2025-11-15",
|
reservation_date="2025-11-15",
|
||||||
reservation_type="reservation",
|
reservation_type="reservation",
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_002",
|
guest_id="101",
|
||||||
first_name="Bob",
|
first_name="Bob",
|
||||||
last_name="Smith",
|
last_name="Smith",
|
||||||
email="bob@example.com",
|
email="bob@example.com",
|
||||||
@@ -446,13 +446,13 @@ class TestConversionServiceWithImportedData:
|
|||||||
res1_v2 = (
|
res1_v2 = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="res_001", # Same ID
|
reservation_id="100", # Same ID
|
||||||
reservation_number="RES-001", # Same number
|
reservation_number="100", # Same number
|
||||||
reservation_date="2025-11-14",
|
reservation_date="2025-11-14",
|
||||||
reservation_type="reservation", # Changed from request
|
reservation_type="reservation", # Changed from request
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_001",
|
guest_id="100",
|
||||||
first_name="Alice",
|
first_name="Alice",
|
||||||
last_name="Johnson",
|
last_name="Johnson",
|
||||||
email="alice@example.com",
|
email="alice@example.com",
|
||||||
@@ -471,13 +471,13 @@ class TestConversionServiceWithImportedData:
|
|||||||
res2_v2 = (
|
res2_v2 = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="res_002", # Same ID
|
reservation_id="101", # Same ID
|
||||||
reservation_number="RES-002", # Same number
|
reservation_number="101", # Same number
|
||||||
reservation_date="2025-11-15",
|
reservation_date="2025-11-15",
|
||||||
reservation_type="request", # Changed from reservation
|
reservation_type="request", # Changed from reservation
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_002",
|
guest_id="101",
|
||||||
first_name="Bob",
|
first_name="Bob",
|
||||||
last_name="Smith",
|
last_name="Smith",
|
||||||
email="bob@example.com",
|
email="bob@example.com",
|
||||||
@@ -554,12 +554,12 @@ class TestXMLBuilderUsage:
|
|||||||
xml_content = (
|
xml_content = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="test_123",
|
reservation_id="123",
|
||||||
reservation_number="RES-123",
|
reservation_number="123",
|
||||||
reservation_date="2025-11-14",
|
reservation_date="2025-11-14",
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_001",
|
guest_id="157",
|
||||||
first_name="John",
|
first_name="John",
|
||||||
last_name="Doe",
|
last_name="Doe",
|
||||||
email="john@example.com",
|
email="john@example.com",
|
||||||
@@ -591,12 +591,12 @@ class TestXMLBuilderUsage:
|
|||||||
xml_content = (
|
xml_content = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="test_456",
|
reservation_id="456",
|
||||||
reservation_number="RES-456",
|
reservation_number="456",
|
||||||
reservation_date="2025-11-14",
|
reservation_date="2025-11-14",
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_002",
|
guest_id="157",
|
||||||
first_name="Jane",
|
first_name="Jane",
|
||||||
last_name="Smith",
|
last_name="Smith",
|
||||||
email="jane@example.com",
|
email="jane@example.com",
|
||||||
@@ -634,12 +634,12 @@ class TestXMLBuilderUsage:
|
|||||||
res1 = (
|
res1 = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="test_001",
|
reservation_id="175",
|
||||||
reservation_number="RES-001",
|
reservation_number="175",
|
||||||
reservation_date="2025-11-14",
|
reservation_date="2025-11-14",
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_001",
|
guest_id="157",
|
||||||
first_name="Alice",
|
first_name="Alice",
|
||||||
last_name="Johnson",
|
last_name="Johnson",
|
||||||
email="alice@example.com",
|
email="alice@example.com",
|
||||||
@@ -656,12 +656,12 @@ class TestXMLBuilderUsage:
|
|||||||
res2 = (
|
res2 = (
|
||||||
ReservationXMLBuilder(
|
ReservationXMLBuilder(
|
||||||
hotel_id="39054_001",
|
hotel_id="39054_001",
|
||||||
reservation_id="test_002",
|
reservation_id="2725",
|
||||||
reservation_number="RES-002",
|
reservation_number="RES-002",
|
||||||
reservation_date="2025-11-15",
|
reservation_date="2025-11-15",
|
||||||
)
|
)
|
||||||
.set_guest(
|
.set_guest(
|
||||||
guest_id="guest_002",
|
guest_id="2525",
|
||||||
first_name="Bob",
|
first_name="Bob",
|
||||||
last_name="Williams",
|
last_name="Williams",
|
||||||
email="bob@example.com",
|
email="bob@example.com",
|
||||||
@@ -752,10 +752,12 @@ class TestHashedMatchingLogic:
|
|||||||
test_db_session.add(reservation)
|
test_db_session.add(reservation)
|
||||||
await test_db_session.commit()
|
await test_db_session.commit()
|
||||||
|
|
||||||
|
PMS_RESERVATION_ID = 157
|
||||||
|
|
||||||
# Create conversion XML with matching hashed data
|
# Create conversion XML with matching hashed data
|
||||||
xml_content = """<?xml version="1.0"?>
|
xml_content = f"""<?xml version="1.0"?>
|
||||||
<root>
|
<root>
|
||||||
<reservation id="pms_123" hotelID="hotel_1" number="RES001" date="2025-01-15">
|
<reservation id="{PMS_RESERVATION_ID}" hotelID="hotel_1" number="378" date="2025-01-15">
|
||||||
<guest id="123" firstName="David" lastName="Miller" email="david@example.com"/>
|
<guest id="123" firstName="David" lastName="Miller" email="david@example.com"/>
|
||||||
<roomReservations>
|
<roomReservations>
|
||||||
<roomReservation roomNumber="101" arrival="2025-01-15" departure="2025-01-17" status="confirmed">
|
<roomReservation roomNumber="101" arrival="2025-01-15" departure="2025-01-17" status="confirmed">
|
||||||
@@ -772,7 +774,9 @@ class TestHashedMatchingLogic:
|
|||||||
|
|
||||||
# Verify conversion was created
|
# Verify conversion was created
|
||||||
result = await test_db_session.execute(
|
result = await test_db_session.execute(
|
||||||
select(Conversion).where(Conversion.pms_reservation_id == "pms_123")
|
select(Conversion).where(
|
||||||
|
Conversion.pms_reservation_id == PMS_RESERVATION_ID
|
||||||
|
)
|
||||||
)
|
)
|
||||||
conversion = result.scalar_one_or_none()
|
conversion = result.scalar_one_or_none()
|
||||||
|
|
||||||
@@ -785,7 +789,7 @@ class TestHashedMatchingLogic:
|
|||||||
|
|
||||||
result_with_guest = await test_db_session.execute(
|
result_with_guest = await test_db_session.execute(
|
||||||
select(Conversion)
|
select(Conversion)
|
||||||
.where(Conversion.pms_reservation_id == "pms_123")
|
.where(Conversion.pms_reservation_id == PMS_RESERVATION_ID)
|
||||||
.options(selectinload(Conversion.guest))
|
.options(selectinload(Conversion.guest))
|
||||||
)
|
)
|
||||||
conversion_with_guest = result_with_guest.scalar_one_or_none()
|
conversion_with_guest = result_with_guest.scalar_one_or_none()
|
||||||
|
|||||||
Reference in New Issue
Block a user