Skip to main content
Glama
setup.ts7 kB
/** * Hostaway API mocking infrastructure using nock * * Provides mock HTTP responses for: * - Authentication (POST /v1/accessTokens) * - Listings/properties (GET /v1/listings) * - Bookings (GET /v1/reservations) * - Financial reports (GET /v1/financialReports) * - Pagination responses * - Error scenarios (500 HTML, 429 rate limits) */ import nock from 'nock'; import { generateProperties, generateBookings, generateLargeFinancialReport, generateLargeHtmlError } from '../generators/index.js'; const HOSTAWAY_API_BASE = 'https://api.hostaway.com'; /** * Setup basic Hostaway API authentication mock * * Mocks successful OAuth token response */ export function setupAuthMock(): nock.Scope { return nock(HOSTAWAY_API_BASE) .post('/v1/accessTokens') .reply(200, { access_token: 'mock_access_token_12345', token_type: 'Bearer', expires_in: 3600, }) .persist(); // Keep this mock active for all tests } export interface ListingsMockOptions { /** Total number of properties to mock */ total?: number; /** Page size (limit parameter) */ limit?: number; /** Current offset */ offset?: number; } /** * Setup listings/properties endpoint mock with pagination support * * @param options - Mock configuration options */ export function setupListingsMock(options: ListingsMockOptions = {}): nock.Scope { const { total = 100, limit = 50, offset = 0, } = options; const properties = generateProperties(total); const page = properties.slice(offset, offset + limit); const hasMore = offset + limit < total; return nock(HOSTAWAY_API_BASE) .get('/v1/listings') .query(true) // Match any query parameters .reply(200, (uri, requestBody) => { // Parse query parameters from the URI const url = new URL(uri, HOSTAWAY_API_BASE); const queryLimit = parseInt(url.searchParams.get('limit') || String(limit), 10); const queryOffset = parseInt(url.searchParams.get('offset') || '0', 10); // Return page based on actual query params const responsePage = properties.slice(queryOffset, queryOffset + queryLimit); const responseHasMore = queryOffset + queryLimit < total; return { status: 'success', result: responsePage, count: responsePage.length, limit: queryLimit, offset: queryOffset, hasMore: responseHasMore, nextCursor: responseHasMore ? Buffer.from(JSON.stringify({ offset: queryOffset + queryLimit })).toString('base64') : undefined, }; }) .persist(); } export interface BookingsMockOptions { /** Total number of bookings to mock */ total?: number; /** Page size (limit parameter) */ limit?: number; /** Current offset */ offset?: number; } /** * Setup bookings/reservations endpoint mock with pagination support * * @param options - Mock configuration options */ export function setupBookingsMock(options: BookingsMockOptions = {}): nock.Scope { const { total = 100, limit = 50, offset = 0, } = options; const bookings = generateBookings(total); const page = bookings.slice(offset, offset + limit); const hasMore = offset + limit < total; return nock(HOSTAWAY_API_BASE) .get('/v1/reservations') .query(true) // Match any query parameters .reply(200, (uri, requestBody) => { // Parse query parameters from the URI const url = new URL(uri, HOSTAWAY_API_BASE); const queryLimit = parseInt(url.searchParams.get('limit') || String(limit), 10); const queryOffset = parseInt(url.searchParams.get('offset') || '0', 10); // Return page based on actual query params const responsePage = bookings.slice(queryOffset, queryOffset + queryLimit); const responseHasMore = queryOffset + queryLimit < total; return { status: 'success', result: responsePage, count: responsePage.length, limit: queryLimit, offset: queryOffset, hasMore: responseHasMore, nextCursor: responseHasMore ? Buffer.from(JSON.stringify({ offset: queryOffset + queryLimit })).toString('base64') : undefined, }; }) .persist(); } /** * Setup large financial report mock (triggers preview mode) * * Returns an oversized financial report that should trigger * preview/summarization in the MCP server. */ export function setupLargeFinancialReportMock(): nock.Scope { const largeReport = generateLargeFinancialReport(); return nock(HOSTAWAY_API_BASE) .get('/v1/financialReports') .query(true) // Match any query parameters .reply(200, largeReport); } /** * Setup error mocks for testing error hygiene * * Includes: * - 500 Internal Server Error with large HTML body * - 429 Rate Limit with Retry-After header * - 403 Forbidden * - 404 Not Found */ export function setupErrorMocks(): void { // 500 error with large HTML body (should be stripped by MCP server) nock(HOSTAWAY_API_BASE) .get('/v1/listings') .query({ trigger_error: '500' }) .reply(500, generateLargeHtmlError(), { 'Content-Type': 'text/html', }); // 429 rate limit with Retry-After header nock(HOSTAWAY_API_BASE) .get('/v1/listings') .query({ trigger_error: '429' }) .reply(429, { error: 'Rate limit exceeded', message: 'Too many requests. Please retry after the specified time.', }, { 'Retry-After': '60', 'X-RateLimit-Limit': '1000', 'X-RateLimit-Remaining': '0', 'X-RateLimit-Reset': String(Date.now() + 60000), }); // 403 forbidden nock(HOSTAWAY_API_BASE) .get('/v1/financialReports') .query({ trigger_error: '403' }) .reply(403, { error: 'Forbidden', message: 'Your account does not have permission to access financial reports.', }); // 404 not found nock(HOSTAWAY_API_BASE) .get('/v1/listings/999999') .reply(404, { error: 'Not found', message: 'Property not found.', }); } /** * Setup all common Hostaway API mocks * * Convenience function to set up authentication, listings, bookings, and error mocks. */ export function setupHostawayMocks(options: { listings?: ListingsMockOptions; bookings?: BookingsMockOptions; includeErrors?: boolean; } = {}): void { setupAuthMock(); if (options.listings !== undefined) { setupListingsMock(options.listings); } if (options.bookings !== undefined) { setupBookingsMock(options.bookings); } if (options.includeErrors) { setupErrorMocks(); } } /** * Clear all nock mocks * * Call this in test cleanup to ensure mocks don't leak between tests. */ export function clearHostawayMocks(): void { nock.cleanAll(); } /** * Verify all expected nock mocks were called * * Call this at the end of tests to ensure expected HTTP calls were made. */ export function verifyHostawayMocks(): void { if (!nock.isDone()) { const pending = nock.pendingMocks(); throw new Error(`Pending mocks not called: ${pending.join(', ')}`); } }

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