Merge branch 'db_fixes_plus_free_rooms' of https://gitea.99tales.net/jonas/alpinebits_python into db_fixes_plus_free_rooms
This commit is contained in:
@@ -98,7 +98,7 @@ def sample_reservation(sample_customer):
|
||||
user_comment="Late check-in requested",
|
||||
fbclid="PAZXh0bgNhZW0BMABhZGlkAasmYBTNE3QBp1jWuJ9zIpfEGRJMP63fMAMI405yvG5EtH-OT0PxSkAbBJaudFHR6cMtkdHu_aem_fopaFtECyVPNW9fmWfEkyA",
|
||||
gclid="",
|
||||
hotel_code="HOTEL123",
|
||||
hotel_id="HOTEL123",
|
||||
hotel_name="Alpine Paradise Resort",
|
||||
)
|
||||
data = reservation.model_dump(exclude_none=True)
|
||||
@@ -136,7 +136,7 @@ def minimal_reservation(minimal_customer):
|
||||
num_adults=1,
|
||||
num_children=0,
|
||||
children_ages=[],
|
||||
hotel_code="HOTEL123",
|
||||
hotel_id="HOTEL123",
|
||||
created_at=datetime(2024, 12, 2, 12, 0, 0, tzinfo=UTC),
|
||||
hotel_name="Alpine Paradise Resort",
|
||||
)
|
||||
@@ -403,7 +403,7 @@ class TestEdgeCases:
|
||||
num_adults=1,
|
||||
num_children=0,
|
||||
children_ages="",
|
||||
hotel_code="HOTEL123",
|
||||
hotel_id="HOTEL123",
|
||||
created_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
@@ -434,7 +434,7 @@ class TestEdgeCases:
|
||||
num_adults=2,
|
||||
num_children=0,
|
||||
children_ages=[],
|
||||
hotel_code="HOTEL123",
|
||||
hotel_id="HOTEL123",
|
||||
created_at=datetime.now(UTC),
|
||||
utm_source="facebook",
|
||||
utm_medium="social",
|
||||
@@ -851,7 +851,7 @@ class TestAcknowledgments:
|
||||
num_adults=2,
|
||||
num_children=0,
|
||||
children_ages=[],
|
||||
hotel_code="HOTEL123",
|
||||
hotel_id="HOTEL123",
|
||||
hotel_name="Alpine Paradise Resort",
|
||||
created_at=datetime(2024, 11, 1, 12, 0, 0, tzinfo=UTC),
|
||||
)
|
||||
@@ -863,7 +863,7 @@ class TestAcknowledgments:
|
||||
num_adults=2,
|
||||
num_children=1,
|
||||
children_ages=[10],
|
||||
hotel_code="HOTEL123",
|
||||
hotel_id="HOTEL123",
|
||||
hotel_name="Alpine Paradise Resort",
|
||||
created_at=datetime(2024, 11, 15, 10, 0, 0, tzinfo=UTC),
|
||||
)
|
||||
|
||||
@@ -523,7 +523,7 @@ class TestGenericWebhookEndpoint:
|
||||
(r for r in reservations if r.customer_id == customer.id), None
|
||||
)
|
||||
assert reservation is not None, "Reservation should be created"
|
||||
assert reservation.hotel_code == "HOTEL123"
|
||||
assert reservation.hotel_id == "HOTEL123"
|
||||
assert reservation.hotel_name == "Test Hotel"
|
||||
assert reservation.num_adults == 2
|
||||
assert reservation.num_children == 1
|
||||
@@ -614,7 +614,7 @@ class TestGenericWebhookEndpoint:
|
||||
result = await session.execute(select(Reservation))
|
||||
reservations = result.scalars().all()
|
||||
reservation = next(
|
||||
(r for r in reservations if r.hotel_code == "HOTEL123"), None
|
||||
(r for r in reservations if r.hotel_id == "HOTEL123"), None
|
||||
)
|
||||
assert reservation is not None, "Reservation should be created"
|
||||
assert reservation.num_children == 3
|
||||
|
||||
@@ -20,6 +20,7 @@ import pytest
|
||||
import pytest_asyncio
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from alpine_bits_python.conversion_service import ConversionService
|
||||
from alpine_bits_python.csv_import import CSVImporter
|
||||
@@ -142,7 +143,7 @@ class TestConversionServiceWithImportedData:
|
||||
|
||||
## Need to check if reservations and customers are now actually available in the db before proceeding
|
||||
|
||||
conversion_service = ConversionService(test_db_session)
|
||||
conversion_service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await conversion_service.process_conversion_xml(xml_content)
|
||||
|
||||
# BASELINE ASSERTIONS:
|
||||
@@ -224,7 +225,7 @@ class TestConversionServiceWithImportedData:
|
||||
# File already has proper XML structure, just use it as-is
|
||||
xml_content = xml_content.strip()
|
||||
|
||||
conversion_service = ConversionService(test_db_session)
|
||||
conversion_service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await conversion_service.process_conversion_xml(xml_content)
|
||||
|
||||
# Verify conversions were created
|
||||
@@ -300,7 +301,7 @@ class TestConversionServiceWithImportedData:
|
||||
# File already has proper XML structure, just use it as-is
|
||||
xml_content = xml_content.strip()
|
||||
|
||||
conversion_service = ConversionService(test_db_session)
|
||||
conversion_service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await conversion_service.process_conversion_xml(xml_content)
|
||||
|
||||
# Verify conversions were processed
|
||||
@@ -332,7 +333,7 @@ class TestConversionServiceWithImportedData:
|
||||
"""Test ConversionService handles invalid XML gracefully."""
|
||||
invalid_xml = "<invalid>unclosed tag"
|
||||
|
||||
conversion_service = ConversionService(test_db_session)
|
||||
conversion_service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid XML"):
|
||||
await conversion_service.process_conversion_xml(invalid_xml)
|
||||
@@ -342,7 +343,7 @@ class TestConversionServiceWithImportedData:
|
||||
"""Test ConversionService handles empty/minimal XML."""
|
||||
minimal_xml = '<?xml version="1.0"?><root></root>'
|
||||
|
||||
conversion_service = ConversionService(test_db_session)
|
||||
conversion_service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await conversion_service.process_conversion_xml(minimal_xml)
|
||||
|
||||
assert stats["total_reservations"] == 0
|
||||
@@ -421,7 +422,7 @@ class TestConversionServiceWithImportedData:
|
||||
xml_content1 = multi_builder1.build_xml()
|
||||
|
||||
# Process first batch
|
||||
service = ConversionService(test_db_session)
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats1 = await service.process_conversion_xml(xml_content1)
|
||||
|
||||
assert stats1["total_reservations"] == 2
|
||||
@@ -542,6 +543,187 @@ class TestConversionServiceWithImportedData:
|
||||
)
|
||||
|
||||
|
||||
class TestConversionUpdatesAndMatching:
|
||||
"""Tests covering conversion updates and core matching logic."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reprocessing_conversion_updates_metadata(self, test_db_session):
|
||||
"""Ensure reprocessing a reservation updates metadata instead of duplicating."""
|
||||
def build_xml(
|
||||
*,
|
||||
booking_channel: str,
|
||||
advertising_medium: str,
|
||||
advertising_partner: str,
|
||||
room_number: str,
|
||||
arrival: str,
|
||||
departure: str,
|
||||
revenue: float,
|
||||
) -> str:
|
||||
return f"""<?xml version="1.0"?>
|
||||
<root>
|
||||
<reservation id="2001" hotelID="39054_001" number="A-1" date="2025-01-05"
|
||||
bookingChannel="{booking_channel}"
|
||||
advertisingMedium="{advertising_medium}"
|
||||
advertisingPartner="{advertising_partner}"
|
||||
advertisingCampagne="abc123">
|
||||
<guest id="900" firstName="Casey" lastName="Jordan" email="casey@example.com"/>
|
||||
<roomReservations>
|
||||
<roomReservation roomNumber="{room_number}" arrival="{arrival}" departure="{departure}" status="reserved">
|
||||
<dailySales>
|
||||
<dailySale date="{arrival}" revenueTotal="{revenue}"/>
|
||||
<dailySale date="{departure}" revenueTotal="{revenue}"/>
|
||||
</dailySales>
|
||||
</roomReservation>
|
||||
</roomReservations>
|
||||
</reservation>
|
||||
</root>"""
|
||||
|
||||
first_xml = build_xml(
|
||||
booking_channel="OTA",
|
||||
advertising_medium="META",
|
||||
advertising_partner="cpc",
|
||||
room_number="33",
|
||||
arrival="2025-02-01",
|
||||
departure="2025-02-03",
|
||||
revenue=120.0,
|
||||
)
|
||||
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats_first = await service.process_conversion_xml(first_xml)
|
||||
assert stats_first["total_reservations"] == 1
|
||||
|
||||
result = await test_db_session.execute(
|
||||
select(Conversion)
|
||||
.where(
|
||||
Conversion.hotel_id == "39054_001",
|
||||
Conversion.pms_reservation_id == 2001,
|
||||
)
|
||||
.options(selectinload(Conversion.conversion_rooms))
|
||||
)
|
||||
conversion = result.scalar_one()
|
||||
assert conversion.booking_channel == "OTA"
|
||||
assert conversion.advertising_partner == "cpc"
|
||||
original_room_count = len(conversion.conversion_rooms)
|
||||
assert original_room_count == 1
|
||||
assert conversion.conversion_rooms[0].room_number == "33"
|
||||
|
||||
updated_xml = build_xml(
|
||||
booking_channel="DIRECT",
|
||||
advertising_medium="WEBSITE",
|
||||
advertising_partner="organic",
|
||||
room_number="44",
|
||||
arrival="2025-02-02",
|
||||
departure="2025-02-04",
|
||||
revenue=150.0,
|
||||
)
|
||||
|
||||
stats_second = await service.process_conversion_xml(updated_xml)
|
||||
assert stats_second["total_reservations"] == 1
|
||||
|
||||
test_db_session.expire_all()
|
||||
result = await test_db_session.execute(
|
||||
select(Conversion)
|
||||
.where(
|
||||
Conversion.hotel_id == "39054_001",
|
||||
Conversion.pms_reservation_id == 2001,
|
||||
)
|
||||
.options(selectinload(Conversion.conversion_rooms))
|
||||
)
|
||||
updated_conversion = result.scalar_one()
|
||||
assert updated_conversion.booking_channel == "DIRECT"
|
||||
assert updated_conversion.advertising_medium == "WEBSITE"
|
||||
assert updated_conversion.advertising_partner == "organic"
|
||||
assert len(updated_conversion.conversion_rooms) == 1
|
||||
assert updated_conversion.conversion_rooms[0].room_number == "44"
|
||||
assert updated_conversion.conversion_rooms[0].arrival_date.strftime(
|
||||
"%Y-%m-%d"
|
||||
) == "2025-02-02"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_advertising_match_uses_hashed_email_for_disambiguation(
|
||||
self, test_db_session
|
||||
):
|
||||
"""Ensure hashed email filters ambiguous advertising matches."""
|
||||
# Create two customers/reservations sharing the same click-id prefix
|
||||
customer_a = Customer(
|
||||
given_name="Lara",
|
||||
surname="North",
|
||||
email_address="lara@example.com",
|
||||
contact_id="contact_a",
|
||||
)
|
||||
customer_a.update_hashed_fields()
|
||||
customer_b = Customer(
|
||||
given_name="Mia",
|
||||
surname="West",
|
||||
email_address="mia@example.com",
|
||||
contact_id="contact_b",
|
||||
)
|
||||
customer_b.update_hashed_fields()
|
||||
|
||||
test_db_session.add_all([customer_a, customer_b])
|
||||
await test_db_session.flush()
|
||||
|
||||
reservation_a = Reservation(
|
||||
customer_id=customer_a.id,
|
||||
unique_id="res_a",
|
||||
md5_unique_id="A" * 32,
|
||||
hotel_id="39054_001",
|
||||
fbclid="click-prefix-111",
|
||||
)
|
||||
reservation_b = Reservation(
|
||||
customer_id=customer_b.id,
|
||||
unique_id="res_b",
|
||||
md5_unique_id="B" * 32,
|
||||
hotel_id="39054_001",
|
||||
fbclid="click-prefix-222",
|
||||
)
|
||||
test_db_session.add_all([reservation_a, reservation_b])
|
||||
await test_db_session.commit()
|
||||
|
||||
from tests.helpers import ReservationXMLBuilder
|
||||
|
||||
xml_content = (
|
||||
ReservationXMLBuilder(
|
||||
hotel_id="39054_001",
|
||||
reservation_id="3001",
|
||||
reservation_number="B-1",
|
||||
reservation_date="2025-03-10",
|
||||
advertising_campagne="click-prefix",
|
||||
)
|
||||
.set_guest(
|
||||
guest_id="701",
|
||||
first_name="Mia",
|
||||
last_name="West",
|
||||
email="mia@example.com",
|
||||
)
|
||||
.add_room(
|
||||
arrival="2025-04-01",
|
||||
departure="2025-04-03",
|
||||
room_number="55",
|
||||
status="reserved",
|
||||
revenue_logis_per_day=180.0,
|
||||
)
|
||||
.build_xml()
|
||||
)
|
||||
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await service.process_conversion_xml(xml_content)
|
||||
|
||||
result = await test_db_session.execute(
|
||||
select(Conversion)
|
||||
.where(
|
||||
Conversion.hotel_id == "39054_001",
|
||||
Conversion.pms_reservation_id == 3001,
|
||||
)
|
||||
.options(selectinload(Conversion.guest))
|
||||
)
|
||||
conversion = result.scalar_one()
|
||||
assert conversion.reservation_id == reservation_b.id
|
||||
assert conversion.customer_id == customer_b.id
|
||||
assert stats["matched_to_reservation"] == 1
|
||||
assert stats["matched_to_customer"] == 0
|
||||
|
||||
|
||||
class TestXMLBuilderUsage:
|
||||
"""Demonstrate usage of XML builder helpers for creating test data."""
|
||||
|
||||
@@ -577,7 +759,7 @@ class TestXMLBuilderUsage:
|
||||
)
|
||||
|
||||
# Process the XML
|
||||
service = ConversionService(test_db_session)
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await service.process_conversion_xml(xml_content)
|
||||
|
||||
assert stats["total_reservations"] == 1
|
||||
@@ -616,7 +798,7 @@ class TestXMLBuilderUsage:
|
||||
.build_xml()
|
||||
)
|
||||
|
||||
service = ConversionService(test_db_session)
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await service.process_conversion_xml(xml_content)
|
||||
|
||||
assert stats["total_reservations"] == 1
|
||||
@@ -677,7 +859,7 @@ class TestXMLBuilderUsage:
|
||||
xml_content = multi_builder.build_xml()
|
||||
|
||||
# Process the XML
|
||||
service = ConversionService(test_db_session)
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await service.process_conversion_xml(xml_content)
|
||||
|
||||
assert stats["total_reservations"] == 2
|
||||
@@ -740,14 +922,13 @@ class TestHashedMatchingLogic:
|
||||
test_db_session.add(customer)
|
||||
await test_db_session.flush()
|
||||
|
||||
hashed_customer = customer.create_hashed_customer()
|
||||
test_db_session.add(hashed_customer)
|
||||
await test_db_session.flush()
|
||||
customer.update_hashed_fields()
|
||||
|
||||
|
||||
reservation = Reservation(
|
||||
customer_id=customer.id,
|
||||
unique_id="res_6",
|
||||
hotel_code="hotel_1",
|
||||
hotel_id="hotel_1",
|
||||
)
|
||||
test_db_session.add(reservation)
|
||||
await test_db_session.commit()
|
||||
@@ -769,7 +950,7 @@ class TestHashedMatchingLogic:
|
||||
</reservation>
|
||||
</root>"""
|
||||
|
||||
service = ConversionService(test_db_session, hotel_id="hotel_1")
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
stats = await service.process_conversion_xml(xml_content)
|
||||
|
||||
# Verify conversion was created
|
||||
@@ -800,17 +981,152 @@ class TestHashedMatchingLogic:
|
||||
assert conversion_with_guest.guest.guest_last_name == "Miller"
|
||||
assert conversion_with_guest.guest.guest_email == "david@example.com"
|
||||
|
||||
# Verify conversion_room was created
|
||||
room_result = await test_db_session.execute(
|
||||
select(ConversionRoom).where(ConversionRoom.conversion_id == conversion.id)
|
||||
)
|
||||
rooms = room_result.scalars().all()
|
||||
assert len(rooms) > 0, "ConversionRoom should be created"
|
||||
|
||||
# Verify matching occurred (may or may not have matched depending on data)
|
||||
# The important thing is that the records exist
|
||||
assert stats["total_reservations"] == 1
|
||||
assert stats["total_daily_sales"] == 1
|
||||
class TestRegularGuestClassification:
|
||||
"""Tests for the classify_regular_guests helper."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_classify_regular_guest_with_unattributable_history(
|
||||
self, test_db_session
|
||||
):
|
||||
"""Guests with unattributable paying stays become regulars."""
|
||||
from tests.helpers import MultiReservationXMLBuilder, ReservationXMLBuilder
|
||||
|
||||
multi = MultiReservationXMLBuilder()
|
||||
base_builder = ReservationXMLBuilder(
|
||||
hotel_id="39054_001",
|
||||
reservation_id="4001",
|
||||
reservation_number="REG-1",
|
||||
reservation_date="2025-05-01",
|
||||
).set_guest(
|
||||
guest_id="888",
|
||||
first_name="Regular",
|
||||
last_name="Guest",
|
||||
email="regular@example.com",
|
||||
)
|
||||
base_builder.add_room(
|
||||
arrival="2025-06-01",
|
||||
departure="2025-06-03",
|
||||
room_number="71",
|
||||
status="departed",
|
||||
revenue_logis_per_day=220.0,
|
||||
)
|
||||
multi.add_reservation(base_builder)
|
||||
|
||||
second = ReservationXMLBuilder(
|
||||
hotel_id="39054_001",
|
||||
reservation_id="4002",
|
||||
reservation_number="REG-2",
|
||||
reservation_date="2025-05-10",
|
||||
).set_guest(
|
||||
guest_id="888",
|
||||
first_name="Regular",
|
||||
last_name="Guest",
|
||||
email="regular@example.com",
|
||||
)
|
||||
second.add_room(
|
||||
arrival="2025-07-01",
|
||||
departure="2025-07-04",
|
||||
room_number="72",
|
||||
status="departed",
|
||||
revenue_logis_per_day=210.0,
|
||||
)
|
||||
multi.add_reservation(second)
|
||||
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
await service.process_conversion_xml(multi.build_xml())
|
||||
|
||||
stats = await service.classify_regular_guests(updated_within_hours=None)
|
||||
assert stats["regular"] == 1
|
||||
|
||||
guest = await test_db_session.execute(
|
||||
select(ConversionGuest).where(
|
||||
ConversionGuest.hotel_id == "39054_001",
|
||||
ConversionGuest.guest_id == 888,
|
||||
)
|
||||
)
|
||||
guest_record = guest.scalar_one()
|
||||
assert guest_record.is_regular is True
|
||||
assert guest_record.is_awareness_guest is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_classify_awareness_guest_when_first_stay_attributable(
|
||||
self, test_db_session
|
||||
):
|
||||
"""If the earliest paying stay is attributable, mark awareness guests."""
|
||||
from tests.helpers import MultiReservationXMLBuilder, ReservationXMLBuilder
|
||||
|
||||
multi = MultiReservationXMLBuilder()
|
||||
first = ReservationXMLBuilder(
|
||||
hotel_id="39054_001",
|
||||
reservation_id="4101",
|
||||
reservation_number="AW-1",
|
||||
reservation_date="2025-08-01",
|
||||
).set_guest(
|
||||
guest_id="889",
|
||||
first_name="Aware",
|
||||
last_name="Guest",
|
||||
email="aware@example.com",
|
||||
)
|
||||
first.add_room(
|
||||
arrival="2025-09-01",
|
||||
departure="2025-09-03",
|
||||
room_number="81",
|
||||
status="departed",
|
||||
revenue_logis_per_day=250.0,
|
||||
)
|
||||
multi.add_reservation(first)
|
||||
|
||||
second = ReservationXMLBuilder(
|
||||
hotel_id="39054_001",
|
||||
reservation_id="4102",
|
||||
reservation_number="AW-2",
|
||||
reservation_date="2025-08-10",
|
||||
).set_guest(
|
||||
guest_id="889",
|
||||
first_name="Aware",
|
||||
last_name="Guest",
|
||||
email="aware@example.com",
|
||||
)
|
||||
second.add_room(
|
||||
arrival="2025-10-05",
|
||||
departure="2025-10-08",
|
||||
room_number="82",
|
||||
status="departed",
|
||||
revenue_logis_per_day=260.0,
|
||||
)
|
||||
multi.add_reservation(second)
|
||||
|
||||
service = ConversionService(test_db_session, hotel_id="39054_001")
|
||||
await service.process_conversion_xml(multi.build_xml())
|
||||
|
||||
# Mark earliest stay as attributable to simulate campaign match
|
||||
result = await test_db_session.execute(
|
||||
select(Conversion)
|
||||
.where(
|
||||
Conversion.hotel_id == "39054_001",
|
||||
Conversion.guest_id == 889,
|
||||
)
|
||||
.order_by(Conversion.reservation_date.asc())
|
||||
)
|
||||
conversions = result.scalars().all()
|
||||
conversions[0].directly_attributable = True
|
||||
conversions[1].directly_attributable = False
|
||||
await test_db_session.commit()
|
||||
|
||||
stats = await service.classify_regular_guests(updated_within_hours=None)
|
||||
assert stats["regular"] == 1
|
||||
assert stats["awareness"] == 1
|
||||
|
||||
guest = await test_db_session.execute(
|
||||
select(ConversionGuest).where(
|
||||
ConversionGuest.hotel_id == "39054_001",
|
||||
ConversionGuest.guest_id == 889,
|
||||
)
|
||||
)
|
||||
guest_record = guest.scalar_one()
|
||||
assert guest_record.is_regular is True
|
||||
assert guest_record.is_awareness_guest is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_conversion_guest_composite_key_prevents_duplicates(
|
||||
|
||||
@@ -6,7 +6,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
|
||||
from alpine_bits_python.customer_service import CustomerService
|
||||
from alpine_bits_python.db import Base, Customer, HashedCustomer
|
||||
from alpine_bits_python.db import Base, Customer
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
@@ -42,9 +42,9 @@ async def test_create_customer_creates_hashed_version(async_session: AsyncSessio
|
||||
assert customer.given_name == "John"
|
||||
|
||||
# Check that hashed version was created
|
||||
hashed = await service.get_hashed_customer(customer.id)
|
||||
hashed = await service.get_customer(customer.id)
|
||||
assert hashed is not None
|
||||
assert hashed.customer_id == customer.id
|
||||
assert hashed.id == customer.id
|
||||
assert hashed.hashed_email is not None
|
||||
assert hashed.hashed_phone is not None
|
||||
assert hashed.hashed_given_name is not None
|
||||
@@ -66,7 +66,7 @@ async def test_update_customer_updates_hashed_version(async_session: AsyncSessio
|
||||
customer = await service.create_customer(customer_data)
|
||||
|
||||
# Get initial hashed email
|
||||
hashed = await service.get_hashed_customer(customer.id)
|
||||
hashed = await service.get_customer(customer.id)
|
||||
original_hashed_email = hashed.hashed_email
|
||||
|
||||
# Update customer email
|
||||
@@ -74,7 +74,7 @@ async def test_update_customer_updates_hashed_version(async_session: AsyncSessio
|
||||
updated_customer = await service.update_customer(customer, update_data)
|
||||
|
||||
# Check that hashed version was updated
|
||||
updated_hashed = await service.get_hashed_customer(updated_customer.id)
|
||||
updated_hashed = await service.get_customer(updated_customer.id)
|
||||
assert updated_hashed.hashed_email != original_hashed_email
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ async def test_get_or_create_customer_creates_new(async_session: AsyncSession):
|
||||
assert customer.contact_id == "new123"
|
||||
|
||||
# Verify hashed version exists
|
||||
hashed = await service.get_hashed_customer(customer.id)
|
||||
hashed = await service.get_customer(customer.id)
|
||||
assert hashed is not None
|
||||
|
||||
|
||||
@@ -145,10 +145,13 @@ async def test_hash_existing_customers_backfills(async_session: AsyncSession):
|
||||
|
||||
# Verify no hashed version exists
|
||||
result = await async_session.execute(
|
||||
select(HashedCustomer).where(HashedCustomer.customer_id == customer.id)
|
||||
select(Customer).where(Customer.id == customer.id)
|
||||
)
|
||||
hashed = result.scalar_one_or_none()
|
||||
assert hashed is None
|
||||
assert hashed, "Customer should exist."
|
||||
|
||||
assert hashed.hashed_given_name is None, "Hashed given name should be None."
|
||||
assert hashed.hashed_email is None, "Hashed email should be None."
|
||||
|
||||
# Run backfill
|
||||
service = CustomerService(async_session)
|
||||
@@ -158,11 +161,12 @@ async def test_hash_existing_customers_backfills(async_session: AsyncSession):
|
||||
|
||||
# Verify hashed version now exists
|
||||
result = await async_session.execute(
|
||||
select(HashedCustomer).where(HashedCustomer.customer_id == customer.id)
|
||||
select(Customer).where(Customer.id == customer.id)
|
||||
)
|
||||
hashed = result.scalar_one_or_none()
|
||||
assert hashed is not None
|
||||
assert hashed.hashed_email is not None
|
||||
assert hashed is not None, "Customer should still exist after backfill."
|
||||
assert hashed.hashed_email is not None, "Hashed email should be populated."
|
||||
assert hashed.hashed_given_name is not None, "Hashed given name should be populated."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -201,7 +205,7 @@ async def test_hashing_normalization(async_session: AsyncSession):
|
||||
}
|
||||
customer = await service.create_customer(customer_data)
|
||||
|
||||
hashed = await service.get_hashed_customer(customer.id)
|
||||
hashed = await service.get_customer(customer.id)
|
||||
|
||||
# Verify hashes exist (normalization should have occurred)
|
||||
assert hashed.hashed_email is not None
|
||||
@@ -244,13 +248,17 @@ async def test_hash_existing_customers_normalizes_country_code(
|
||||
|
||||
# Verify no hashed version exists yet
|
||||
result = await async_session.execute(
|
||||
select(HashedCustomer).where(HashedCustomer.customer_id == customer.id)
|
||||
select(Customer).where(Customer.id == customer.id)
|
||||
)
|
||||
hashed = result.scalar_one_or_none()
|
||||
assert hashed is None
|
||||
assert hashed is not None, "Customer should exist."
|
||||
|
||||
assert hashed.hashed_given_name is None, "Hashed given name should be None."
|
||||
assert hashed.hashed_email is None, "Hashed email should be None."
|
||||
assert hashed.hashed_country_code is None, "Hashed country code should be None."
|
||||
|
||||
# Verify the customer has the invalid country code stored in the DB
|
||||
assert customer.country_code == "Italy"
|
||||
assert hashed.country_code == "Italy"
|
||||
|
||||
# Run hash_existing_customers - this should normalize "Italy" to "IT"
|
||||
# during validation and successfully create a hashed customer
|
||||
@@ -263,7 +271,7 @@ async def test_hash_existing_customers_normalizes_country_code(
|
||||
# Verify hashed version was created
|
||||
await async_session.refresh(customer)
|
||||
result = await async_session.execute(
|
||||
select(HashedCustomer).where(HashedCustomer.customer_id == customer.id)
|
||||
select(Customer).where(Customer.id == customer.id)
|
||||
)
|
||||
hashed = result.scalar_one_or_none()
|
||||
assert hashed is not None
|
||||
@@ -302,7 +310,7 @@ async def test_hash_existing_customers_normalizes_country_code(
|
||||
|
||||
# Verify hashed version was created with correct hash
|
||||
result = await async_session.execute(
|
||||
select(HashedCustomer).where(HashedCustomer.customer_id == customer2.id)
|
||||
select(Customer).where(Customer.id == customer2.id)
|
||||
)
|
||||
hashed = result.scalar_one_or_none()
|
||||
assert hashed is not None
|
||||
|
||||
Reference in New Issue
Block a user