Skip to main content
Glama
test_listings_summary.py9.25 kB
"""Integration tests for listings API summary parameter. Tests the summary=true query parameter on GET /api/listings endpoint. Verifies: - Summarized response returns only essential fields - Backward compatibility (no summary parameter) - Response size reduction - Note field presence in metadata """ from unittest.mock import AsyncMock, patch import pytest 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_full_listing_response() -> list[dict]: """Create mock Hostaway full listing response with all fields.""" return [ { "id": 12345, "name": "Luxury Villa in Seminyak", "address": "123 Sunset Blvd", "city": "Seminyak", "state": "Bali", "country": "Indonesia", "postal_code": "80361", "description": "Stunning 3-bedroom villa with private pool...", "capacity": 6, "bedrooms": 3, "bathrooms": 2.5, "property_type": "villa", "base_price": 250.00, "isActive": True, "amenities": ["Pool", "WiFi", "Kitchen", "Air Conditioning"], "images": ["https://example.com/img1.jpg", "https://example.com/img2.jpg"], }, { "id": 12346, "name": "Beachfront Apartment", "address": "456 Ocean Ave", "city": "Canggu", "state": "Bali", "country": "Indonesia", "postal_code": "80351", "description": "Modern 2-bedroom apartment steps from beach...", "capacity": 4, "bedrooms": 2, "bathrooms": 2, "property_type": "apartment", "base_price": 180.00, "isActive": False, # Inactive listing "amenities": ["WiFi", "Kitchen", "Parking"], "images": ["https://example.com/img3.jpg"], }, ] @pytest.mark.asyncio async def test_get_listings_with_summary_true( test_config: HostawayConfig, mock_full_listing_response: list[dict] ) -> None: """Test GET /listings?summary=true returns summarized response. Verifies: - Returns 200 OK - Response items contain only summarized fields - metadata.note field is present - Status field is derived from isActive """ from fastapi.testclient import TestClient from src.api.main import app with patch("src.mcp.auth.get_authenticated_client") as mock_get_client: mock_client = AsyncMock() mock_client.config = test_config mock_client.get_listings = AsyncMock(return_value=mock_full_listing_response) mock_get_client.return_value = mock_client client = TestClient(app) response = client.get("/api/listings?summary=true&limit=10") assert response.status_code == 200 data = response.json() assert "items" in data assert "meta" in data assert "nextCursor" in data # Verify metadata contains note field assert data["meta"]["note"] is not None assert "GET /api/listings/{id}" in data["meta"]["note"] # Verify first item has only summarized fields first_item = data["items"][0] assert set(first_item.keys()) == {"id", "name", "city", "country", "bedrooms", "status"} assert first_item["id"] == 12345 assert first_item["name"] == "Luxury Villa in Seminyak" assert first_item["city"] == "Seminyak" assert first_item["country"] == "Indonesia" assert first_item["bedrooms"] == 3 assert first_item["status"] == "Available" # Derived from isActive=True # Verify second item (inactive listing) second_item = data["items"][1] assert second_item["id"] == 12346 assert second_item["status"] == "Inactive" # Derived from isActive=False # Verify no full fields are present assert "description" not in first_item assert "amenities" not in first_item assert "address" not in first_item assert "base_price" not in first_item @pytest.mark.asyncio async def test_get_listings_without_summary_returns_full_response( test_config: HostawayConfig, mock_full_listing_response: list[dict] ) -> None: """Test GET /listings (no summary parameter) returns full response. Verifies backward compatibility: - Returns 200 OK - Response items contain all fields - metadata.note field is NOT present """ from fastapi.testclient import TestClient from src.api.main import app with patch("src.mcp.auth.get_authenticated_client") as mock_get_client: mock_client = AsyncMock() mock_client.config = test_config mock_client.get_listings = AsyncMock(return_value=mock_full_listing_response) mock_get_client.return_value = mock_client client = TestClient(app) response = client.get("/api/listings?limit=10") assert response.status_code == 200 data = response.json() assert "items" in data # Verify first item has full fields first_item = data["items"][0] assert first_item["id"] == 12345 assert "description" in first_item assert "amenities" in first_item assert "address" in first_item assert "base_price" in first_item # Verify metadata does NOT contain note field (or is None) assert data["meta"].get("note") is None @pytest.mark.asyncio async def test_get_listings_summary_false_returns_full_response( test_config: HostawayConfig, mock_full_listing_response: list[dict] ) -> None: """Test GET /listings?summary=false returns full response. Verifies: - Explicit summary=false behaves same as no parameter """ from fastapi.testclient import TestClient from src.api.main import app with patch("src.mcp.auth.get_authenticated_client") as mock_get_client: mock_client = AsyncMock() mock_client.config = test_config mock_client.get_listings = AsyncMock(return_value=mock_full_listing_response) mock_get_client.return_value = mock_client client = TestClient(app) response = client.get("/api/listings?summary=false&limit=10") assert response.status_code == 200 data = response.json() first_item = data["items"][0] # Verify full fields present assert "description" in first_item assert "amenities" in first_item @pytest.mark.asyncio async def test_get_listings_summary_with_null_optional_fields(test_config: HostawayConfig) -> None: """Test summary mode handles null optional fields gracefully. Verifies: - city=None is accepted - country=None is accepted - bedrooms defaults to 0 if missing """ from fastapi.testclient import TestClient from src.api.main import app mock_listing_with_nulls = [ { "id": 1, "name": "Test Property", "city": None, # Null city "country": None, # Null country # bedrooms missing "isActive": True, } ] with patch("src.mcp.auth.get_authenticated_client") as mock_get_client: mock_client = AsyncMock() mock_client.config = test_config mock_client.get_listings = AsyncMock(return_value=mock_listing_with_nulls) mock_get_client.return_value = mock_client client = TestClient(app) response = client.get("/api/listings?summary=true") assert response.status_code == 200 data = response.json() first_item = data["items"][0] assert first_item["city"] is None assert first_item["country"] is None assert first_item["bedrooms"] == 0 # Default value @pytest.mark.asyncio async def test_get_listings_summary_response_size_reduction( test_config: HostawayConfig, mock_full_listing_response: list[dict] ) -> None: """Test summary mode achieves significant response size reduction. Verifies: - Summary response is at least 70% smaller than full response """ import json from fastapi.testclient import TestClient from src.api.main import app with patch("src.mcp.auth.get_authenticated_client") as mock_get_client: mock_client = AsyncMock() mock_client.config = test_config mock_client.get_listings = AsyncMock(return_value=mock_full_listing_response) mock_get_client.return_value = mock_client client = TestClient(app) # Get full response full_response = client.get("/api/listings?limit=10") full_size = len(json.dumps(full_response.json())) # Get summary response summary_response = client.get("/api/listings?summary=true&limit=10") summary_size = len(json.dumps(summary_response.json())) # Calculate reduction percentage reduction = ((full_size - summary_size) / full_size) * 100 # Verify at least 70% reduction (target is 80-90%) assert reduction >= 70, f"Expected >=70% reduction, got {reduction:.2f}%"

Latest Blog Posts

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