Skip to main content
Glama

Hostaway MCP Server

test_listings_api.py13.1 kB
"""Integration tests for Hostaway listings API. Tests property listing retrieval, details, and availability endpoints. Following TDD: These tests should FAIL until implementation is complete. """ import httpx import pytest from unittest.mock import AsyncMock, MagicMock from src.mcp.auth import TokenManager from src.mcp.config import HostawayConfig @pytest.fixture def test_config(monkeypatch: pytest.MonkeyPatch) -> HostawayConfig: """Create test configuration.""" monkeypatch.setenv("HOSTAWAY_ACCOUNT_ID", "test_account_123") monkeypatch.setenv("HOSTAWAY_SECRET_KEY", "test_secret_key_456") monkeypatch.setenv("HOSTAWAY_API_BASE_URL", "https://api.hostaway.com/v1") return HostawayConfig() # type: ignore[call-arg] @pytest.fixture def mock_listing_response() -> dict: """Create mock Hostaway listing response.""" return { "id": 12345, "name": "Cozy Downtown Apartment", "address": "123 Main St, Apt 4B", "city": "San Francisco", "state": "California", "country": "USA", "postal_code": "94102", "description": "Beautiful 2BR apartment...", "capacity": 4, "bedrooms": 2, "bathrooms": 1.5, "property_type": "apartment", "base_price": 150.00, "is_active": True, } @pytest.fixture def mock_availability_response() -> dict: """Create mock Hostaway availability response.""" return { "status": "success", "result": [ { "date": "2024-01-15", "status": "available", "price": 150.00, "min_stay": 2, }, { "date": "2024-01-16", "status": "blocked", "price": 150.00, "min_stay": 2, }, { "date": "2024-01-17", "status": "booked", "price": 150.00, "min_stay": 2, }, ], } # T039: Contract test for GET /listings endpoint class TestListingsEndpoint: """Test GET /listings endpoint contract.""" @pytest.mark.asyncio async def test_get_listings_success( self, test_config: HostawayConfig, mock_listing_response: dict ) -> None: """Test successful retrieval of property listings. Verifies: - GET /listings returns 200 - Response includes array of listings - Pagination parameters work """ mock_client = AsyncMock(spec=httpx.AsyncClient) mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "status": "success", "result": [mock_listing_response], "count": 1, "limit": 100, "offset": 0, } mock_response.raise_for_status = MagicMock() mock_client.get = AsyncMock(return_value=mock_response) # TODO: Import HostawayClient once implemented # from src.mcp.client import HostawayClient # # client = HostawayClient(config=test_config, http_client=mock_client) # listings = await client.get_listings() # # assert len(listings) == 1 # assert listings[0].id == 12345 # assert listings[0].name == "Cozy Downtown Apartment" # assert listings[0].city == "San Francisco" # # # Verify request was made correctly # mock_client.get.assert_called_once_with( # "/listings", # params={"limit": 100, "offset": 0} # ) @pytest.mark.asyncio async def test_get_listings_pagination( self, test_config: HostawayConfig, mock_listing_response: dict ) -> None: """Test pagination works for GET /listings. Verifies: - limit parameter controls page size - offset parameter controls starting position - count field shows total results """ mock_client = AsyncMock(spec=httpx.AsyncClient) mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "status": "success", "result": [mock_listing_response], "count": 50, # Total count "limit": 10, "offset": 20, } mock_response.raise_for_status = MagicMock() mock_client.get = AsyncMock(return_value=mock_response) # TODO: Import HostawayClient once implemented # from src.mcp.client import HostawayClient # # client = HostawayClient(config=test_config, http_client=mock_client) # listings = await client.get_listings(limit=10, offset=20) # # # Verify pagination parameters # mock_client.get.assert_called_once_with( # "/listings", # params={"limit": 10, "offset": 20} # ) # T040: Contract test for GET /listings/{id} endpoint class TestListingDetailsEndpoint: """Test GET /listings/{id} endpoint contract.""" @pytest.mark.asyncio async def test_get_listing_by_id_success( self, test_config: HostawayConfig, mock_listing_response: dict ) -> None: """Test successful retrieval of single property details. Verifies: - GET /listings/{id} returns 200 - Response includes complete listing details - All required fields present """ listing_id = 12345 mock_client = AsyncMock(spec=httpx.AsyncClient) mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "status": "success", "result": mock_listing_response, } mock_response.raise_for_status = MagicMock() mock_client.get = AsyncMock(return_value=mock_response) # TODO: Import HostawayClient once implemented # from src.mcp.client import HostawayClient # # client = HostawayClient(config=test_config, http_client=mock_client) # listing = await client.get_listing(listing_id) # # assert listing.id == 12345 # assert listing.name == "Cozy Downtown Apartment" # assert listing.city == "San Francisco" # assert listing.capacity == 4 # assert listing.bedrooms == 2 # # # Verify request was made correctly # mock_client.get.assert_called_once_with(f"/listings/{listing_id}") @pytest.mark.asyncio async def test_get_listing_not_found( self, test_config: HostawayConfig ) -> None: """Test GET /listings/{id} returns 404 for invalid ID. Verifies: - Invalid listing ID returns 404 - Error response is properly formatted """ listing_id = 99999 mock_client = AsyncMock(spec=httpx.AsyncClient) mock_response = MagicMock() mock_response.status_code = 404 mock_response.json.return_value = { "error": "not_found", "error_description": "Listing not found", "status": 404, } mock_response.raise_for_status.side_effect = httpx.HTTPStatusError( "404 Not Found", request=MagicMock(), response=mock_response, ) mock_client.get = AsyncMock(return_value=mock_response) # TODO: Import HostawayClient once implemented # from src.mcp.client import HostawayClient # # client = HostawayClient(config=test_config, http_client=mock_client) # # with pytest.raises(httpx.HTTPStatusError) as exc_info: # await client.get_listing(listing_id) # # assert exc_info.value.response.status_code == 404 # T041: Contract test for GET /listings/{id}/calendar endpoint class TestListingAvailabilityEndpoint: """Test GET /listings/{id}/calendar endpoint contract.""" @pytest.mark.asyncio async def test_get_listing_availability_success( self, test_config: HostawayConfig, mock_availability_response: dict ) -> None: """Test successful retrieval of property availability. Verifies: - GET /listings/{id}/calendar returns 200 - Response includes available/blocked dates - Date range parameters work correctly """ listing_id = 12345 start_date = "2024-01-15" end_date = "2024-01-31" mock_client = AsyncMock(spec=httpx.AsyncClient) mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = mock_availability_response mock_response.raise_for_status = MagicMock() mock_client.get = AsyncMock(return_value=mock_response) # TODO: Import HostawayClient once implemented # from src.mcp.client import HostawayClient # # client = HostawayClient(config=test_config, http_client=mock_client) # availability = await client.get_listing_availability( # listing_id, start_date=start_date, end_date=end_date # ) # # assert len(availability) == 3 # assert availability[0]["date"] == "2024-01-15" # assert availability[0]["status"] == "available" # assert availability[1]["status"] == "blocked" # assert availability[2]["status"] == "booked" # # # Verify request was made correctly # mock_client.get.assert_called_once_with( # f"/listings/{listing_id}/calendar", # params={"startDate": start_date, "endDate": end_date} # ) # T042: Integration test for property listing flow class TestPropertyListingFlow: """Test complete property listing flow.""" @pytest.mark.asyncio async def test_listing_flow_auth_to_details( self, test_config: HostawayConfig, mock_listing_response: dict, mock_availability_response: dict, ) -> None: """Test complete flow: authenticate → list properties → get details. Flow: 1. Authenticate and get token 2. List all properties 3. Get details for specific property 4. Check availability Verifies end-to-end integration works. """ mock_client = AsyncMock(spec=httpx.AsyncClient) # Mock authentication response mock_auth_response = MagicMock() mock_auth_response.status_code = 200 mock_auth_response.json.return_value = { "access_token": "test_token_abc123_xyz789_20chars", "token_type": "Bearer", "expires_in": 63072000, "scope": "general", } mock_auth_response.raise_for_status = MagicMock() # Mock listings list response mock_listings_response = MagicMock() mock_listings_response.status_code = 200 mock_listings_response.json.return_value = { "status": "success", "result": [mock_listing_response], "count": 1, "limit": 100, "offset": 0, } mock_listings_response.raise_for_status = MagicMock() # Mock listing details response mock_details_response = MagicMock() mock_details_response.status_code = 200 mock_details_response.json.return_value = { "status": "success", "result": mock_listing_response, } mock_details_response.raise_for_status = MagicMock() # Mock availability response mock_avail_response = MagicMock() mock_avail_response.status_code = 200 mock_avail_response.json.return_value = mock_availability_response mock_avail_response.raise_for_status = MagicMock() # Setup mock responses mock_client.post = AsyncMock(return_value=mock_auth_response) mock_client.get = AsyncMock( side_effect=[ mock_listings_response, mock_details_response, mock_avail_response, ] ) # TODO: Import and test full flow once implemented # from src.mcp.auth import TokenManager # from src.mcp.client import HostawayClient # # # Step 1: Authenticate # token_manager = TokenManager(config=test_config, client=mock_client) # token = await token_manager.get_token() # assert token.access_token == "test_token_abc123_xyz789_20chars" # # # Step 2: List properties # client = HostawayClient(config=test_config, http_client=mock_client) # listings = await client.get_listings() # assert len(listings) == 1 # # # Step 3: Get property details # listing_id = listings[0].id # details = await client.get_listing(listing_id) # assert details.id == 12345 # assert details.name == "Cozy Downtown Apartment" # # # Step 4: Check availability # availability = await client.get_listing_availability(listing_id) # assert len(availability) == 3 # assert availability[0]["status"] == "available"

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/darrentmorgan/hostaway-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server