diff --git a/logs/push_requests/alpinebits_push_12345_c52702c9-55b9-44e1-b158-ec9544c73cc7_20251007_160537.xml b/logs/push_requests/alpinebits_push_12345_c52702c9-55b9-44e1-b158-ec9544c73cc7_20251007_160537.xml new file mode 100644 index 0000000..f10a59b --- /dev/null +++ b/logs/push_requests/alpinebits_push_12345_c52702c9-55b9-44e1-b158-ec9544c73cc7_20251007_160537.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + Frau + Genesia + Supino + + + supinogenesia@gmail.com + + + + + + + + + + + + + + + 99tales GmbH + + + + + + + + + diff --git a/logs/wix_test_data_20251007_160537.json b/logs/wix_test_data_20251007_160537.json new file mode 100644 index 0000000..b5d4e7e --- /dev/null +++ b/logs/wix_test_data_20251007_160537.json @@ -0,0 +1,257 @@ +{ + "timestamp": "2025-10-07T16:05:37.531417", + "client_ip": "127.0.0.1", + "headers": { + "host": "localhost:8080", + "content-type": "application/json", + "user-agent": "insomnia/2023.5.8", + "accept": "*/*", + "content-length": "7335" + }, + "data": { + "data": { + "formName": "Contact us", + "submissions": [ + { + "label": "Anreisedatum", + "value": "2026-01-02" + }, + { + "label": "Abreisedatum", + "value": "2026-01-07" + }, + { + "label": "Anzahl Erwachsene", + "value": "3" + }, + { + "label": "Anzahl Kinder", + "value": "1" + }, + { + "label": "Alter Kind 1", + "value": "12" + }, + { + "label": "Anrede", + "value": "Frau" + }, + { + "label": "Vorname", + "value": "Genesia " + }, + { + "label": "Nachname", + "value": "Supino " + }, + { + "label": "Email", + "value": "supinogenesia@gmail.com" + }, + { + "label": "Phone", + "value": "+39 340 625 9979" + }, + { + "label": "Einwilligung Marketing", + "value": "Selezionato" + }, + { + "label": "utm_Source", + "value": "fb" + }, + { + "label": "utm_Medium", + "value": "Facebook_Mobile_Feed" + }, + { + "label": "utm_Campaign", + "value": "Conversions_Hotel_Bemelmans_ITA" + }, + { + "label": "utm_Term", + "value": "Cold_Traffic_Conversions_Hotel_Bemelmans_ITA" + }, + { + "label": "utm_Content", + "value": "Grafik_AuszeitDezember_9.12_23.12" + }, + { + "label": "utm_term_id", + "value": "120238574626400196" + }, + { + "label": "utm_content_id", + "value": "120238574626400196" + }, + { + "label": "gad_source", + "value": "" + }, + { + "label": "gad_campaignid", + "value": "" + }, + { + "label": "gbraid", + "value": "" + }, + { + "label": "gclid", + "value": "" + }, + { + "label": "fbclid", + "value": "IwZXh0bgNhZW0BMABhZGlkAassWPh1b8QBHoRc2S24gMktdNKiPwEvGYMK3rB-mne_0IJQvQRIGH60wLvLfOm0XWP8wJ9s_aem_rbpAFMODwOh4UnF5UVxwWg" + }, + { + "label": "hotelid", + "value": "12345" + }, + { + "label": "hotelname", + "value": "Bemelmans Post" + } + ], + "field:date_picker_7e65": "2026-01-07", + "field:number_7cf5": "3", + "field:utm_source": "fb", + "submissionTime": "2025-10-07T05:48:41.855Z", + "field:alter_kind_3": "12", + "field:gad_source": "", + "field:form_field_5a7b": "Selezionato", + "field:gad_campaignid": "", + "field:utm_medium": "Facebook_Mobile_Feed", + "field:utm_term_id": "120238574626400196", + "context": { + "metaSiteId": "1dea821c-8168-4736-96e4-4b92e8b364cf", + "activationId": "2421c9cd-6565-49ba-b60f-165d3dacccba" + }, + "field:email_5139": "supinogenesia@gmail.com", + "field:phone_4c77": "+39 340 625 9979", + "_context": { + "activation": { + "id": "2421c9cd-6565-49ba-b60f-165d3dacccba" + }, + "configuration": { + "id": "a976f18c-fa86-495d-be1e-676df188eeae" + }, + "app": { + "id": "225dd912-7dea-4738-8688-4b8c6955ffc2" + }, + "action": { + "id": "152db4d7-5263-40c4-be2b-1c81476318b7" + }, + "trigger": { + "key": "wix_form_app-form_submitted" + } + }, + "field:gclid": "", + "formFieldMask": [ + "field:", + "field:", + "field:angebot_auswaehlen", + "field:date_picker_a7c8", + "field:date_picker_7e65", + "field:", + "field:number_7cf5", + "field:anzahl_kinder", + "field:alter_kind_3", + "field:alter_kind_25", + "field:alter_kind_4", + "field:alter_kind_5", + "field:alter_kind_6", + "field:alter_kind_7", + "field:alter_kind_8", + "field:alter_kind_9", + "field:alter_kind_10", + "field:alter_kind_11", + "field:", + "field:anrede", + "field:first_name_abae", + "field:last_name_d97c", + "field:email_5139", + "field:phone_4c77", + "field:long_answer_3524", + "field:form_field_5a7b", + "field:", + "field:utm_source", + "field:utm_medium", + "field:utm_campaign", + "field:utm_term", + "field:utm_content", + "field:utm_term_id", + "field:utm_content_id", + "field:gad_source", + "field:gad_campaignid", + "field:gbraid", + "field:gclid", + "field:fbclid", + "field:hotelid", + "field:hotelname", + "field:", + "metaSiteId" + ], + "contact": { + "name": { + "first": "Genesia", + "last": "Supino" + }, + "email": "supinogenesia@gmail.com", + "locale": "it-it", + "phones": [ + { + "tag": "UNTAGGED", + "formattedPhone": "+39 340 625 9979", + "id": "198f04fb-5b2c-4a7b-b7ea-adc150ec4212", + "countryCode": "IT", + "e164Phone": "+393406259979", + "primary": true, + "phone": "340 625 9979" + } + ], + "contactId": "4d695011-36c1-4480-b225-ae9c6eef9e83", + "emails": [ + { + "id": "e09d7bab-1f11-4b5d-b3c5-32d43c1dc584", + "tag": "UNTAGGED", + "email": "supinogenesia@gmail.com", + "primary": true + } + ], + "updatedDate": "2025-10-07T05:48:44.764Z", + "phone": "+393406259979", + "createdDate": "2025-10-07T05:48:43.567Z" + }, + "submissionId": "c52702c9-55b9-44e1-b158-ec9544c73cc7", + "field:anzahl_kinder": "1", + "field:first_name_abae": "Genesia ", + "field:utm_content_id": "120238574626400196", + "field:utm_campaign": "Conversions_Hotel_Bemelmans_ITA", + "field:utm_term": "Cold_Traffic_Conversions_Hotel_Bemelmans_ITA", + "contactId": "4d695011-36c1-4480-b225-ae9c6eef9e83", + "field:date_picker_a7c8": "2026-01-02", + "field:hotelname": "Bemelmans Post", + "field:utm_content": "Grafik_AuszeitDezember_9.12_23.12", + "field:last_name_d97c": "Supino ", + "field:hotelid": "12345", + "submissionsLink": "https://manage.wix.app/forms/submissions/1dea821c-8168-4736-96e4-4b92e8b364cf/e084006b-ae83-4e4d-b2f5-074118cdb3b1?d=https%3A%2F%2Fmanage.wix.com%2Fdashboard%2F1dea821c-8168-4736-96e4-4b92e8b364cf%2Fwix-forms%2Fform%2Fe084006b-ae83-4e4d-b2f5-074118cdb3b1%2Fsubmissions&s=true", + "field:gbraid": "", + "field:fbclid": "IwZXh0bgNhZW0BMABhZGlkAassWPh1b8QBHoRc2S24gMktdNKiPwEvGYMK3rB-mne_0IJQvQRIGH60wLvLfOm0XWP8wJ9s_aem_rbpAFMODwOh4UnF5UVxwWg", + "submissionPdf": { + "fileName": "c52702c9-55b9-44e1-b158-ec9544c73cc7.pdf", + "downloadUrl": "https://manage.wix.com/_api/form-submission-service/v4/submissions/c52702c9-55b9-44e1-b158-ec9544c73cc7/download?accessToken=JWS.eyJraWQiOiJWLVNuLWhwZSIsImFsZyI6IkhTMjU2In0.eyJkYXRhIjoie1wibWV0YVNpdGVJZFwiOlwiMWRlYTgyMWMtODE2OC00NzM2LTk2ZTQtNGI5MmU4YjM2NGNmXCJ9IiwiaWF0IjoxNzU5ODE2MTI0LCJleHAiOjE3NTk4MTY3MjR9.quBfp9UL9Ddqb2CWERXoVkh9OdmHlIBvlLAyhoXElaY" + }, + "field:anrede": "Frau", + "formId": "e084006b-ae83-4e4d-b2f5-074118cdb3b1" + } + }, + "origin_header": null, + "all_headers": { + "host": "localhost:8080", + "content-type": "application/json", + "user-agent": "insomnia/2023.5.8", + "accept": "*/*", + "content-length": "7335" + } +} \ No newline at end of file diff --git a/src/alpine_bits_python/alpine_bits_helpers.py b/src/alpine_bits_python/alpine_bits_helpers.py index f5d0489..b5ab013 100644 --- a/src/alpine_bits_python/alpine_bits_helpers.py +++ b/src/alpine_bits_python/alpine_bits_helpers.py @@ -6,9 +6,7 @@ from enum import Enum from typing import Any from alpine_bits_python.db import Customer, Reservation -from alpine_bits_python.schemas import ( - HotelReservationIdData as HotelReservationIdDataValidated, -) +from alpine_bits_python.schemas import HotelReservationIdData # Import the generated classes from .generated.alpinebits import ( @@ -327,16 +325,6 @@ class CustomerFactory: ) -@dataclass -class HotelReservationIdData: - """Hold hotel reservation ID information without nested type constraints.""" - - res_id_type: str # Required field - pattern: [0-9]+ - res_id_value: None | str = None # Max 64 characters - res_id_source: None | str = None # Max 64 characters - res_id_source_context: None | str = None # Max 64 characters - - class HotelReservationIdFactory: """Factory class to create HotelReservationId instances for both OtaHotelResNotifRq and OtaResRetrieveRs.""" @@ -770,21 +758,13 @@ def _process_single_reservation( # - Trim whitespace # - Truncate to 64 characters if needed # - Convert empty strings to None - hotel_res_id_data_validated = HotelReservationIdDataValidated( + hotel_res_id_data = HotelReservationIdData( res_id_type="13", res_id_value=klick_id, res_id_source=res_id_source, res_id_source_context="99tales", ) - # Convert back to dataclass for the factory - hotel_res_id_data = HotelReservationIdData( - res_id_type=hotel_res_id_data_validated.res_id_type, - res_id_value=hotel_res_id_data_validated.res_id_value, - res_id_source=hotel_res_id_data_validated.res_id_source, - res_id_source_context=hotel_res_id_data_validated.res_id_source_context, - ) - hotel_res_id = alpine_bits_factory.create(hotel_res_id_data, message_type) hotel_res_ids = HotelReservation.ResGlobalInfo.HotelReservationIds( hotel_reservation_id=[hotel_res_id] diff --git a/src/alpine_bits_python/schemas.py b/src/alpine_bits_python/schemas.py index cceafa8..84ceb0a 100644 --- a/src/alpine_bits_python/schemas.py +++ b/src/alpine_bits_python/schemas.py @@ -75,9 +75,9 @@ class HotelReservationIdData(BaseModel): """Validated hotel reservation ID data.""" res_id_type: str = Field(..., pattern=r"^[0-9]+$") # Must be numeric string - res_id_value: str | None = None - res_id_source: str | None = None - res_id_source_context: str | None = None + res_id_value: str | None = Field(None, min_length=1, max_length=64) + res_id_source: str | None = Field(None, min_length=1, max_length=64) + res_id_source_context: str | None = Field(None, min_length=1, max_length=64) @field_validator( "res_id_value", "res_id_source", "res_id_source_context", mode="before" diff --git a/tests/test_alpinebits_server_ping.py b/tests/test_alpinebits_server_ping.py index 7c5c101..702ba42 100644 --- a/tests/test_alpinebits_server_ping.py +++ b/tests/test_alpinebits_server_ping.py @@ -17,15 +17,11 @@ def extract_relevant_sections(xml_string): @pytest.mark.asyncio async def test_ping_action_response_matches_expected(): - with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: + with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: server = AlpineBitsServer() - with open( - "test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8" - ) as f: + with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: request_xml = f.read() - with open( - "test/test_data/Handshake-OTA_PingRS.xml", encoding="utf-8" - ) as f: + with open("tests/test_data/Handshake-OTA_PingRS.xml", encoding="utf-8") as f: expected_xml = f.read() client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant") response = await server.handle_request( @@ -56,7 +52,7 @@ async def test_ping_action_response_matches_expected(): @pytest.mark.asyncio async def test_ping_action_response_success(): server = AlpineBitsServer() - with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: + with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: request_xml = f.read() client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant") response = await server.handle_request( @@ -74,7 +70,7 @@ async def test_ping_action_response_success(): @pytest.mark.asyncio async def test_ping_action_response_version_arbitrary(): server = AlpineBitsServer() - with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: + with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: request_xml = f.read() client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant") response = await server.handle_request( @@ -91,7 +87,7 @@ async def test_ping_action_response_version_arbitrary(): @pytest.mark.asyncio async def test_ping_action_response_invalid_action(): server = AlpineBitsServer() - with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: + with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f: request_xml = f.read() client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant") response = await server.handle_request(