UNPKG

url-builder-mcp

Version:

URL Builder MCP Server - Professional URL construction with intelligent parameter handling for Claude Desktop

612 lines (515 loc) 20.7 kB
/** * URL Builder MCP Tests * * Test cases following the implementation plan specifications */ import { buildBookingUrl, buildInternalUrl, buildCompleteUrl, validateUrlParams } from '../urlBuilder'; import { UrlParams, UrlOptions } from '../types'; // Helper function to generate future dates for testing function getFutureDates(daysFromNow = 1, stayDuration = 2) { const checkIn = new Date(); checkIn.setDate(checkIn.getDate() + daysFromNow); const checkOut = new Date(checkIn); checkOut.setDate(checkOut.getDate() + stayDuration); return { checkIn: checkIn.toISOString().split('T')[0], checkOut: checkOut.toISOString().split('T')[0], }; } // Set up environment variables for testing process.env.BOOKING_URL_BASE = 'https://hotels.cloudbeds.com/en/reservas/lmKzDQ'; process.env.DEFAULT_CURRENCY = 'eur'; describe('URL Builder MCP', () => { describe('validateUrlParams', () => { it('should validate correct parameters', () => { const dates = getFutureDates(); const params: UrlParams = { checkIn: dates.checkIn, checkOut: dates.checkOut, }; const result = validateUrlParams(params); expect(result.valid).toBe(true); expect(result.errors).toEqual([]); }); it('should reject missing required fields', () => { const params = {} as UrlParams; const result = validateUrlParams(params); expect(result.valid).toBe(false); expect(result.errors).toContain('checkIn y checkOut son requeridos'); }); it('should reject invalid date formats', () => { const params: UrlParams = { checkIn: '01/06/2024', checkOut: '2025-07-18', }; const result = validateUrlParams(params); expect(result.valid).toBe(false); expect(result.errors).toContain('Formato de checkIn inválido. Use YYYY-MM-DD'); }); it('should reject checkout before checkin', () => { const params: UrlParams = { checkIn: '2025-07-18', checkOut: '2025-07-16', }; const result = validateUrlParams(params); expect(result.valid).toBe(false); expect(result.errors).toContain('La fecha de salida debe ser posterior a la de entrada'); }); it('should reject invalid adults range', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', adults: 25, }; const result = validateUrlParams(params); expect(result.valid).toBe(false); expect(result.errors).toContain('Número de adultos debe estar entre 1 y 20'); }); it('should reject invalid children range', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', children: 15, }; const result = validateUrlParams(params); expect(result.valid).toBe(false); expect(result.errors).toContain('Número de niños debe estar entre 0 y 10'); }); }); describe('buildBookingUrl', () => { it('should build basic localhost booking URL with required fields', () => { // Temporarily set localhost for this test const originalEnv = process.env.BASE_DOMAIN; process.env.BASE_DOMAIN = 'http://localhost:8080'; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const url = buildBookingUrl(params, { addUtmTracking: false }); expect(url).toBe( 'http://localhost:8080/accommodation/reservations?checkIn=2025-07-16&checkOut=2025-07-18&adults=2&guestName=Juan+P%C3%A9rez&guestPhone=%2B34612345678' ); // Restore original environment process.env.BASE_DOMAIN = originalEnv; }); it('should build complete localhost booking URL with all parameters', () => { // Temporarily set localhost for this test const originalEnv = process.env.BOOKING_URL_BASE; process.env.BOOKING_URL_BASE = 'http://localhost:8080'; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', guestEmail: 'juan@email.com', adults: 3, children: 1, roomType: 'garden-deluxe', }; const url = buildBookingUrl(params); expect(url).toContain('adults=3'); expect(url).toContain('children=1'); expect(url).toContain('roomType=garden-deluxe'); expect(url).toContain('guestName=Juan+P%C3%A9rez'); expect(url).toContain('guestPhone=%2B34612345678'); expect(url).toContain('guestEmail=juan%40email.com'); expect(url).toContain('/accommodation/reservations?'); // Restore original environment process.env.BOOKING_URL_BASE = originalEnv; }); it('should not include children=0 in localhost URL', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', children: 0, }; const url = buildBookingUrl(params); expect(url).not.toContain('children=0'); }); it('should use query parameters format for localhost', () => { // Temporarily set localhost for this test const originalEnv = process.env.BOOKING_URL_BASE; process.env.BOOKING_URL_BASE = 'http://localhost:8080'; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const url = buildBookingUrl(params); expect(url).toContain('?'); expect(url).toMatch(/.*\?.*checkIn=.*&checkOut=.*/); expect(url).not.toContain('#'); // Restore original environment process.env.BOOKING_URL_BASE = originalEnv; }); it('should use hash fragment format for external URLs', () => { const originalEnv = process.env.BASE_DOMAIN; process.env.BASE_DOMAIN = 'https://hotels.cloudbeds.com/en/reservas/lmKzDQ'; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const url = buildBookingUrl(params); expect(url).toContain('#'); expect(url).toMatch(/.*#.*checkin=.*&checkout=.*/); expect(url).toContain('currency=eur'); process.env.BASE_DOMAIN = originalEnv; }); it('should throw error for missing required fields', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; expect(() => buildBookingUrl(params)).toThrow('Parámetros inválidos'); }); it('should throw error for invalid date parameters', () => { const params: UrlParams = { checkIn: '2025-07-18', checkOut: '2025-07-16', guestName: 'Juan Pérez', guestPhone: '+34612345678', }; expect(() => buildBookingUrl(params)).toThrow('Parámetros inválidos'); }); }); describe('buildInternalUrl', () => { it('should build basic internal URL', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildInternalUrl(params, { addUtmTracking: false }); expect(url).toBe('/accommodation/reservations?checkIn=2025-07-16&checkOut=2025-07-18'); }); it('should build complete internal URL with guest data', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', adults: 2, children: 1, guestName: 'Juan Pérez', guestEmail: 'juan@email.com', guestPhone: '+34123456789', }; const url = buildInternalUrl(params); expect(url).toContain('adults=2'); expect(url).toContain('children=1'); expect(url).toContain('guestName=Juan+P%C3%A9rez'); expect(url).toContain('guestEmail=juan%40email.com'); expect(url).toContain('guestPhone=%2B34123456789'); }); it('should include roomType parameter in internal URL', () => { const params: UrlParams = { checkIn: '2025-07-21', checkOut: '2025-07-25', adults: 2, children: 1, roomType: 'garden-deluxe', guestName: 'María García López', guestEmail: 'maria.garcia@email.com', guestPhone: '+34665789123', }; const url = buildInternalUrl(params); expect(url).toContain('roomType=garden-deluxe'); expect(url).toContain('checkIn=2025-07-21'); expect(url).toContain('checkOut=2025-07-25'); expect(url).toContain('adults=2'); expect(url).toContain('children=1'); expect(url).toContain('guestName=Mar%C3%ADa+Garc%C3%ADa+L%C3%B3pez'); expect(url).toContain('guestEmail=maria.garcia%40email.com'); expect(url).toContain('guestPhone=%2B34665789123'); }); it('should use query parameters format', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildInternalUrl(params); expect(url).toContain('?'); expect(url).toMatch(/.*\?.*checkIn=.*&checkOut=.*/); }); it('should properly encode special characters', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'José María', guestEmail: 'jose@test.com', }; const url = buildInternalUrl(params); expect(url).toContain('Jos%C3%A9+Mar%C3%ADa'); expect(url).toContain('jose%40test.com'); }); it('should throw error for invalid parameters', () => { const params: UrlParams = { checkIn: '01/06/2024', checkOut: '2025-07-18', }; expect(() => buildInternalUrl(params)).toThrow('Parámetros inválidos'); }); }); describe('Environment Configuration', () => { it('should use environment variable for booking URL base', () => { const originalEnv = process.env.BASE_DOMAIN; process.env.BASE_DOMAIN = 'https://custom.booking.url/test'; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const url = buildBookingUrl(params); expect(url).toContain('https://custom.booking.url/test'); // Restore original environment process.env.BASE_DOMAIN = originalEnv; }); it('should use default localhost URL when environment variable is not set', () => { const originalEnv = process.env.BASE_DOMAIN; const originalMcp = process.env.MCP_BASE_DOMAIN; const originalBooking = process.env.BOOKING_URL_BASE; delete process.env.BASE_DOMAIN; delete process.env.MCP_BASE_DOMAIN; delete process.env.BOOKING_URL_BASE; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const url = buildBookingUrl(params); expect(url).toContain('http://localhost:8080'); expect(url).toContain('/accommodation/reservations?'); // Restore original environment if (originalEnv) process.env.BASE_DOMAIN = originalEnv; if (originalMcp) process.env.MCP_BASE_DOMAIN = originalMcp; if (originalBooking) process.env.BOOKING_URL_BASE = originalBooking; }); }); describe('buildCompleteUrl', () => { beforeEach(() => { // Set up base domain for testing process.env.BASE_DOMAIN = 'https://example.com'; }); afterEach(() => { // Clean up environment delete process.env.BASE_DOMAIN; delete process.env.MCP_BASE_DOMAIN; }); it('should build complete URL with base domain', () => { const url = buildCompleteUrl('/api/test', undefined, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/test'); }); it('should handle base domain with trailing slash', () => { process.env.BASE_DOMAIN = 'https://example.com/'; const url = buildCompleteUrl('/api/test', undefined, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/test'); }); it('should handle relative path without leading slash', () => { const url = buildCompleteUrl('api/test', undefined, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/test'); }); it('should prefer BASE_DOMAIN over MCP_BASE_DOMAIN', () => { process.env.BASE_DOMAIN = 'https://primary.com'; process.env.MCP_BASE_DOMAIN = 'https://secondary.com'; const url = buildCompleteUrl('/api/test', undefined, { addUtmTracking: false }); expect(url).toBe('https://primary.com/api/test'); }); it('should use MCP_BASE_DOMAIN when BASE_DOMAIN is not set', () => { delete process.env.BASE_DOMAIN; process.env.MCP_BASE_DOMAIN = 'https://secondary.com'; const url = buildCompleteUrl('/api/test', undefined, { addUtmTracking: false }); expect(url).toBe('https://secondary.com/api/test'); }); it('should use localhost fallback when no base domain is configured', () => { delete process.env.BASE_DOMAIN; delete process.env.MCP_BASE_DOMAIN; delete process.env.BOOKING_URL_BASE; const url = buildCompleteUrl('/api/test', undefined, { addUtmTracking: false }); expect(url).toBe('http://localhost:8080/api/test'); }); it('should validate parameters when provided', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildCompleteUrl('/api/test', params, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/test?checkIn=2025-07-16&checkOut=2025-07-18'); }); it('should throw error for invalid parameters', () => { const params: UrlParams = { checkIn: '2025-07-18', checkOut: '2025-07-16', }; expect(() => buildCompleteUrl('/api/test', params)).toThrow('Parámetros inválidos'); }); }); describe('buildInternalUrl with options', () => { beforeEach(() => { process.env.BASE_DOMAIN = 'https://example.com'; }); afterEach(() => { delete process.env.BASE_DOMAIN; }); it('should return relative URL by default', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildInternalUrl(params, { addUtmTracking: false }); expect(url).toBe('/accommodation/reservations?checkIn=2025-07-16&checkOut=2025-07-18'); }); it('should return complete URL when useBaseDomain is true', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildInternalUrl(params, { useBaseDomain: true, addUtmTracking: false }); expect(url).toBe('https://example.com/accommodation/reservations?checkIn=2025-07-16&checkOut=2025-07-18'); }); it('should build URL with base domain when useBaseDomain is true', () => { process.env.BASE_DOMAIN = 'https://example.com'; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildInternalUrl(params, { useBaseDomain: true, addUtmTracking: false }); expect(url).toBe('https://example.com/accommodation/reservations?checkIn=2025-07-16&checkOut=2025-07-18'); }); it('should use localhost fallback when useBaseDomain is true but BASE_DOMAIN not configured', () => { delete process.env.BASE_DOMAIN; delete process.env.MCP_BASE_DOMAIN; delete process.env.BOOKING_URL_BASE; const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', }; const url = buildInternalUrl(params, { useBaseDomain: true, addUtmTracking: false }); expect(url).toBe('http://localhost:8080/accommodation/reservations?checkIn=2025-07-16&checkOut=2025-07-18'); }); }); describe('buildCompleteUrl', () => { beforeEach(() => { process.env.BASE_DOMAIN = 'https://example.com'; }); afterEach(() => { delete process.env.BASE_DOMAIN; delete process.env.MCP_BASE_DOMAIN; }); it('should build complete URL with relative path', () => { const url = buildCompleteUrl('/api/bookings', undefined, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/bookings'); }); it('should build complete URL with relative path and parameters', () => { const params: UrlParams = { checkIn: '2025-07-16', checkOut: '2025-07-18', adults: 2, }; const url = buildCompleteUrl('/api/bookings', params, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/bookings?checkIn=2025-07-16&checkOut=2025-07-18&adults=2'); }); it('should add leading slash if missing', () => { const url = buildCompleteUrl('api/bookings', undefined, { addUtmTracking: false }); expect(url).toBe('https://example.com/api/bookings'); }); it('should use localhost fallback when BASE_DOMAIN not configured', () => { delete process.env.BASE_DOMAIN; delete process.env.MCP_BASE_DOMAIN; delete process.env.BOOKING_URL_BASE; const url = buildCompleteUrl('/api/bookings', undefined, { addUtmTracking: false }); expect(url).toBe('http://localhost:8080/api/bookings'); }); it('should validate parameters when provided', () => { const params: UrlParams = { checkIn: 'invalid-date', checkOut: '2025-07-18', }; expect(() => { buildCompleteUrl('/api/bookings', params); }).toThrow('Parámetros inválidos'); }); }); describe('UTM Tracking', () => { beforeEach(() => { process.env.BASE_DOMAIN = 'https://hotels.cloudbeds.com/en/reservas/lmKzDQ'; }); afterEach(() => { delete process.env.BASE_DOMAIN; }); it('should add default UTM parameters to booking URL', () => { const dates = getFutureDates(); const params: UrlParams = { checkIn: dates.checkIn, checkOut: dates.checkOut, guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const url = buildBookingUrl(params); expect(url).toContain('utm_source=claude-desktop'); expect(url).toContain('utm_medium=ai-assistant'); expect(url).toContain('utm_campaign=url-builder-mcp'); expect(url).toContain('utm_content=mcp-generated-link'); }); it('should allow custom UTM parameters', () => { const dates = getFutureDates(); const params: UrlParams = { checkIn: dates.checkIn, checkOut: dates.checkOut, guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const options: UrlOptions = { customUtm: { utm_campaign: 'custom-campaign', utm_source: 'custom-source', }, }; const url = buildBookingUrl(params, options); expect(url).toContain('utm_source=custom-source'); expect(url).toContain('utm_campaign=custom-campaign'); expect(url).toContain('utm_medium=ai-assistant'); // Default should remain }); it('should allow disabling UTM tracking', () => { const dates = getFutureDates(); const params: UrlParams = { checkIn: dates.checkIn, checkOut: dates.checkOut, guestName: 'Juan Pérez', guestPhone: '+34612345678', }; const options: UrlOptions = { addUtmTracking: false, }; const url = buildBookingUrl(params, options); expect(url).not.toContain('utm_'); }); it('should add UTM parameters to internal URLs', () => { const dates = getFutureDates(); const params: UrlParams = { checkIn: dates.checkIn, checkOut: dates.checkOut, }; const url = buildInternalUrl(params); expect(url).toContain('utm_source=claude-desktop'); expect(url).toContain('utm_medium=ai-assistant'); expect(url).toContain('utm_campaign=url-builder-mcp'); }); it('should add UTM parameters to complete URLs', () => { const dates = getFutureDates(); const params: UrlParams = { checkIn: dates.checkIn, checkOut: dates.checkOut, }; const url = buildCompleteUrl('/api/bookings', params); expect(url).toContain('utm_source=claude-desktop'); expect(url).toContain('utm_medium=ai-assistant'); expect(url).toContain('utm_campaign=url-builder-mcp'); }); }); });