notif_report #3
@@ -17,6 +17,7 @@ dependencies = [
|
|||||||
"httpx>=0.28.1",
|
"httpx>=0.28.1",
|
||||||
"lxml>=6.0.1",
|
"lxml>=6.0.1",
|
||||||
"pytest>=8.4.2",
|
"pytest>=8.4.2",
|
||||||
|
"pytest-asyncio>=1.2.0",
|
||||||
"redis>=6.4.0",
|
"redis>=6.4.0",
|
||||||
"ruff>=0.13.1",
|
"ruff>=0.13.1",
|
||||||
"slowapi>=0.1.9",
|
"slowapi>=0.1.9",
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ from typing import Union
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Add the src directory to the path so we can import our modules
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
|
||||||
|
|
||||||
from simplified_access import (
|
|
||||||
|
from alpine_bits_python.alpine_bits_helpers import (
|
||||||
CustomerData,
|
CustomerData,
|
||||||
CustomerFactory,
|
CustomerFactory,
|
||||||
ResGuestFactory,
|
ResGuestFactory,
|
||||||
0
test/test_alpine_bits_server.py
Normal file
0
test/test_alpine_bits_server.py
Normal file
51
test/test_alpinebits_server_ping.py
Normal file
51
test/test_alpinebits_server_ping.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import pytest
|
||||||
|
import asyncio
|
||||||
|
from alpine_bits_python.alpinebits_server import AlpineBitsServer, AlpineBitsClientInfo
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ping_action_response_success():
|
||||||
|
server = AlpineBitsServer()
|
||||||
|
with open("test/test_data/Handshake-OTA_PingRQ.xml", "r", encoding="utf-8") as f:
|
||||||
|
request_xml = f.read()
|
||||||
|
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||||
|
response = await server.handle_request(
|
||||||
|
request_action_name="OTA_Ping:Handshaking",
|
||||||
|
request_xml=request_xml,
|
||||||
|
client_info=client_info,
|
||||||
|
version="2024-10"
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "<OTA_PingRS" in response.xml_content
|
||||||
|
assert "<Success" in response.xml_content
|
||||||
|
assert "Version=" in response.xml_content
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ping_action_response_version_arbitrary():
|
||||||
|
server = AlpineBitsServer()
|
||||||
|
with open("test/test_data/Handshake-OTA_PingRQ.xml", "r", encoding="utf-8") as f:
|
||||||
|
request_xml = f.read()
|
||||||
|
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||||
|
response = await server.handle_request(
|
||||||
|
request_action_name="OTA_Ping:Handshaking",
|
||||||
|
request_xml=request_xml,
|
||||||
|
client_info=client_info,
|
||||||
|
version="2022-10"
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "<OTA_PingRS" in response.xml_content
|
||||||
|
assert "Version=" in response.xml_content
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_ping_action_response_invalid_action():
|
||||||
|
server = AlpineBitsServer()
|
||||||
|
with open("test/test_data/Handshake-OTA_PingRQ.xml", "r", encoding="utf-8") as f:
|
||||||
|
request_xml = f.read()
|
||||||
|
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||||
|
response = await server.handle_request(
|
||||||
|
request_action_name="InvalidAction",
|
||||||
|
request_xml=request_xml,
|
||||||
|
client_info=client_info,
|
||||||
|
version="2024-10"
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert "Error" in response.xml_content
|
||||||
158
test/test_data/Handshake-OTA_PingRQ.xml
Normal file
158
test/test_data/Handshake-OTA_PingRQ.xml
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
AlpineBits 2024-10
|
||||||
|
https://www.alpinebits.org/
|
||||||
|
|
||||||
|
Sample message file for a Handshake request
|
||||||
|
|
||||||
|
Changelog:
|
||||||
|
v. 2024-10 1.2 Example extended with all capabilities and two supported releases
|
||||||
|
v. 2024-10 1.1 Removed the OTA_Ping action
|
||||||
|
v. 2024-10 1.0 added supported version 2024-10 in the example
|
||||||
|
v. 2018-10 1.0 initial example
|
||||||
|
-->
|
||||||
|
|
||||||
|
<OTA_PingRQ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://www.opentravel.org/OTA/2003/05"
|
||||||
|
xsi:schemaLocation="http://www.opentravel.org/OTA/2003/05 OTA_PingRQ.xsd"
|
||||||
|
Version="8.000">
|
||||||
|
<EchoData>
|
||||||
|
{
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "2024-10",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests_StatusUpdate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelInvCountNotif",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelInvCountNotif_accept_rooms",
|
||||||
|
"OTA_HotelInvCountNotif_accept_categories",
|
||||||
|
"OTA_HotelInvCountNotif_accept_deltas",
|
||||||
|
"OTA_HotelInvCountNotif_accept_out_of_market",
|
||||||
|
"OTA_HotelInvCountNotif_accept_out_of_order",
|
||||||
|
"OTA_HotelInvCountNotif_accept_complete_set",
|
||||||
|
"OTA_HotelInvCountNotif_accept_closing_seasons"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveContentNotif_Inventory",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelDescriptiveContentNotif_Inventory_use_rooms",
|
||||||
|
"OTA_HotelDescriptiveContentNotif_Inventory_occupancy_children"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveContentNotif_Info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveInfo_Inventory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveInfo_Info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelRatePlanNotif_RatePlans",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelRatePlanNotif_accept_ArrivalDOW",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_DepartureDOW",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlan_BookingRule",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlan_RoomType_BookingRule",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlan_mixed_BookingRule",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_Supplements",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_FreeNightsOffers",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_FamilyOffers",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_full",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_overlay",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlanJoin",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_OfferRule_BookingOffset",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_OfferRule_DOWLOS"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelRatePlan_BaseRates",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelRatePlan_BaseRates_deltas"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelPostEventNotif_EventReports"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2022-10",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Ping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests_StatusUpdate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelInvCountNotif",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelInvCountNotif_accept_rooms",
|
||||||
|
"OTA_HotelInvCountNotif_accept_categories",
|
||||||
|
"OTA_HotelInvCountNotif_accept_deltas",
|
||||||
|
"OTA_HotelInvCountNotif_accept_out_of_market",
|
||||||
|
"OTA_HotelInvCountNotif_accept_out_of_order",
|
||||||
|
"OTA_HotelInvCountNotif_accept_complete_set",
|
||||||
|
"OTA_HotelInvCountNotif_accept_closing_seasons"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveContentNotif_Inventory",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelDescriptiveContentNotif_Inventory_use_rooms",
|
||||||
|
"OTA_HotelDescriptiveContentNotif_Inventory_occupancy_children"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveContentNotif_Info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveInfo_Inventory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelDescriptiveInfo_Info"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelRatePlanNotif_RatePlans",
|
||||||
|
"supports": [
|
||||||
|
"OTA_HotelRatePlanNotif_accept_ArrivalDOW",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_DepartureDOW",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlan_BookingRule",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlan_RoomType_BookingRule",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlan_mixed_BookingRule",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_Supplements",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_FreeNightsOffers",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_FamilyOffers",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_overlay",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_RatePlanJoin",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_OfferRule_BookingOffset",
|
||||||
|
"OTA_HotelRatePlanNotif_accept_OfferRule_DOWLOS"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</EchoData>
|
||||||
|
</OTA_PingRQ>
|
||||||
87
test/test_data/Handshake-OTA_PingRS.xml
Normal file
87
test/test_data/Handshake-OTA_PingRS.xml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
AlpineBits 2024-10
|
||||||
|
https://www.alpinebits.org/
|
||||||
|
|
||||||
|
Sample message file for a Handshake response
|
||||||
|
|
||||||
|
Changelog:
|
||||||
|
v. 2024-10 1.2 Example extended with all capabilities and two supported releases
|
||||||
|
v. 2024-10 1.1 Removed the OTA_Ping action
|
||||||
|
v. 2024-10 1.0 added supported version 2024-10 in the example
|
||||||
|
v. 2018-10 1.0 initial example
|
||||||
|
-->
|
||||||
|
|
||||||
|
<OTA_PingRS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://www.opentravel.org/OTA/2003/05"
|
||||||
|
xsi:schemaLocation="http://www.opentravel.org/OTA/2003/05 OTA_PingRS.xsd"
|
||||||
|
Version="8.000">
|
||||||
|
<Success/>
|
||||||
|
<Warnings>
|
||||||
|
<Warning Type="11" Status="ALPINEBITS_HANDSHAKE">{
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "2024-10",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2022-10",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Ping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</Warning>
|
||||||
|
</Warnings>
|
||||||
|
<EchoData>{
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "2024-10",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2022-10",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Ping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_HotelResNotif_GuestRequests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "action_OTA_Read"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}</EchoData>
|
||||||
|
</OTA_PingRS>
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Quick test to demonstrate how the ServerCapabilities automatically
|
|
||||||
discovers implemented vs unimplemented actions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from alpine_bits_python.alpinebits_server import (
|
|
||||||
ServerCapabilities,
|
|
||||||
AlpineBitsAction,
|
|
||||||
AlpineBitsActionName,
|
|
||||||
Version,
|
|
||||||
AlpineBitsResponse,
|
|
||||||
HttpStatusCode,
|
|
||||||
)
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
|
|
||||||
class NewImplementedAction(AlpineBitsAction):
|
|
||||||
"""A new action that IS implemented."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.name = AlpineBitsActionName.OTA_HOTEL_DESCRIPTIVE_INFO_INFO
|
|
||||||
self.version = Version.V2024_10
|
|
||||||
|
|
||||||
async def handle(
|
|
||||||
self, action: str, request_xml: str, version: Version
|
|
||||||
) -> AlpineBitsResponse:
|
|
||||||
"""This action is implemented."""
|
|
||||||
return AlpineBitsResponse("Implemented!", HttpStatusCode.OK)
|
|
||||||
|
|
||||||
|
|
||||||
class NewUnimplementedAction(AlpineBitsAction):
|
|
||||||
"""A new action that is NOT implemented (no handle override)."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.name = AlpineBitsActionName.OTA_HOTEL_DESCRIPTIVE_CONTENT_NOTIF_INFO
|
|
||||||
self.version = Version.V2024_10
|
|
||||||
|
|
||||||
# Notice: No handle method override - will use default "not implemented"
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
print("🔍 Testing Action Discovery Logic")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# Create capabilities and see what gets discovered
|
|
||||||
capabilities = ServerCapabilities()
|
|
||||||
|
|
||||||
print("📋 Actions found by discovery:")
|
|
||||||
for action_name in capabilities.get_supported_actions():
|
|
||||||
print(f" ✅ {action_name}")
|
|
||||||
|
|
||||||
print(f"\n📊 Total discovered: {len(capabilities.get_supported_actions())}")
|
|
||||||
|
|
||||||
# Test the new implemented action
|
|
||||||
implemented_action = NewImplementedAction()
|
|
||||||
result = await implemented_action.handle("test", "<xml/>", Version.V2024_10)
|
|
||||||
print(f"\n🟢 NewImplementedAction result: {result.xml_content}")
|
|
||||||
|
|
||||||
# Test the unimplemented action (should use default behavior)
|
|
||||||
unimplemented_action = NewUnimplementedAction()
|
|
||||||
result = await unimplemented_action.handle("test", "<xml/>", Version.V2024_10)
|
|
||||||
print(f"🔴 NewUnimplementedAction result: {result.xml_content}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
14
uv.lock
generated
14
uv.lock
generated
@@ -27,6 +27,7 @@ dependencies = [
|
|||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "lxml" },
|
{ name = "lxml" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-asyncio" },
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
{ name = "ruff" },
|
{ name = "ruff" },
|
||||||
{ name = "slowapi" },
|
{ name = "slowapi" },
|
||||||
@@ -47,6 +48,7 @@ requires-dist = [
|
|||||||
{ name = "httpx", specifier = ">=0.28.1" },
|
{ name = "httpx", specifier = ">=0.28.1" },
|
||||||
{ name = "lxml", specifier = ">=6.0.1" },
|
{ name = "lxml", specifier = ">=6.0.1" },
|
||||||
{ name = "pytest", specifier = ">=8.4.2" },
|
{ name = "pytest", specifier = ">=8.4.2" },
|
||||||
|
{ name = "pytest-asyncio", specifier = ">=1.2.0" },
|
||||||
{ name = "redis", specifier = ">=6.4.0" },
|
{ name = "redis", specifier = ">=6.4.0" },
|
||||||
{ name = "ruff", specifier = ">=0.13.1" },
|
{ name = "ruff", specifier = ">=0.13.1" },
|
||||||
{ name = "slowapi", specifier = ">=0.1.9" },
|
{ name = "slowapi", specifier = ">=0.1.9" },
|
||||||
@@ -559,6 +561,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-asyncio"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user