940 lines
28 KiB
JavaScript
940 lines
28 KiB
JavaScript
// Mock the Google Maps client
|
|
const mockPlaceAutocomplete = jest.fn();
|
|
const mockPlaceDetails = jest.fn();
|
|
const mockGeocode = jest.fn();
|
|
|
|
jest.mock('@googlemaps/google-maps-services-js', () => ({
|
|
Client: jest.fn().mockImplementation(() => ({
|
|
placeAutocomplete: mockPlaceAutocomplete,
|
|
placeDetails: mockPlaceDetails,
|
|
geocode: mockGeocode
|
|
}))
|
|
}));
|
|
|
|
describe('GoogleMapsService', () => {
|
|
let service;
|
|
let consoleSpy, consoleErrorSpy;
|
|
|
|
beforeEach(() => {
|
|
// Clear all mocks
|
|
jest.clearAllMocks();
|
|
|
|
// Set up console spies
|
|
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
|
|
// Reset environment
|
|
delete process.env.GOOGLE_MAPS_API_KEY;
|
|
|
|
// Clear module cache to get fresh instance
|
|
jest.resetModules();
|
|
});
|
|
|
|
afterEach(() => {
|
|
consoleSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
describe('Constructor', () => {
|
|
it('should initialize with API key and log success', () => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
|
|
service = require('../../../services/googleMapsService');
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith('✅ Google Maps service initialized');
|
|
expect(service.isConfigured()).toBe(true);
|
|
});
|
|
|
|
it('should log error when API key is not configured', () => {
|
|
delete process.env.GOOGLE_MAPS_API_KEY;
|
|
|
|
service = require('../../../services/googleMapsService');
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('❌ Google Maps API key not configured in environment variables');
|
|
expect(service.isConfigured()).toBe(false);
|
|
});
|
|
|
|
it('should initialize Google Maps Client', () => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
const { Client } = require('@googlemaps/google-maps-services-js');
|
|
|
|
service = require('../../../services/googleMapsService');
|
|
|
|
expect(Client).toHaveBeenCalledWith({});
|
|
});
|
|
});
|
|
|
|
describe('getPlacesAutocomplete', () => {
|
|
beforeEach(() => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
service = require('../../../services/googleMapsService');
|
|
});
|
|
|
|
describe('Input validation', () => {
|
|
it('should throw error when API key is not configured', async () => {
|
|
service.apiKey = null;
|
|
|
|
await expect(service.getPlacesAutocomplete('test')).rejects.toThrow('Google Maps API key not configured');
|
|
});
|
|
|
|
it('should return empty predictions for empty input', async () => {
|
|
const result = await service.getPlacesAutocomplete('');
|
|
|
|
expect(result).toEqual({ predictions: [] });
|
|
expect(mockPlaceAutocomplete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return empty predictions for input less than 2 characters', async () => {
|
|
const result = await service.getPlacesAutocomplete('a');
|
|
|
|
expect(result).toEqual({ predictions: [] });
|
|
expect(mockPlaceAutocomplete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should trim input and proceed with valid input', async () => {
|
|
mockPlaceAutocomplete.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
predictions: []
|
|
}
|
|
});
|
|
|
|
await service.getPlacesAutocomplete(' test ');
|
|
|
|
expect(mockPlaceAutocomplete).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
input: 'test'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Parameters handling', () => {
|
|
beforeEach(() => {
|
|
mockPlaceAutocomplete.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
predictions: []
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should use default parameters', async () => {
|
|
await service.getPlacesAutocomplete('test input');
|
|
|
|
expect(mockPlaceAutocomplete).toHaveBeenCalledWith({
|
|
params: {
|
|
key: 'test-api-key',
|
|
input: 'test input',
|
|
types: 'address',
|
|
language: 'en'
|
|
},
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should accept custom options', async () => {
|
|
const options = {
|
|
types: 'establishment',
|
|
language: 'fr'
|
|
};
|
|
|
|
await service.getPlacesAutocomplete('test input', options);
|
|
|
|
expect(mockPlaceAutocomplete).toHaveBeenCalledWith({
|
|
params: {
|
|
key: 'test-api-key',
|
|
input: 'test input',
|
|
types: 'establishment',
|
|
language: 'fr'
|
|
},
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should include session token when provided', async () => {
|
|
const options = {
|
|
sessionToken: 'session-123'
|
|
};
|
|
|
|
await service.getPlacesAutocomplete('test input', options);
|
|
|
|
expect(mockPlaceAutocomplete).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
sessiontoken: 'session-123'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should handle component restrictions', async () => {
|
|
const options = {
|
|
componentRestrictions: {
|
|
country: 'us',
|
|
administrative_area: 'CA'
|
|
}
|
|
};
|
|
|
|
await service.getPlacesAutocomplete('test input', options);
|
|
|
|
expect(mockPlaceAutocomplete).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
components: 'country:us|administrative_area:CA'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should merge additional options', async () => {
|
|
const options = {
|
|
radius: 1000,
|
|
location: '40.7128,-74.0060'
|
|
};
|
|
|
|
await service.getPlacesAutocomplete('test input', options);
|
|
|
|
expect(mockPlaceAutocomplete).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
radius: 1000,
|
|
location: '40.7128,-74.0060'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Successful responses', () => {
|
|
it('should return formatted predictions on success', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
predictions: [
|
|
{
|
|
place_id: 'ChIJ123',
|
|
description: 'Test Location, City, State',
|
|
types: ['establishment'],
|
|
structured_formatting: {
|
|
main_text: 'Test Location',
|
|
secondary_text: 'City, State'
|
|
}
|
|
},
|
|
{
|
|
place_id: 'ChIJ456',
|
|
description: 'Another Place',
|
|
types: ['locality'],
|
|
structured_formatting: {
|
|
main_text: 'Another Place'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
mockPlaceAutocomplete.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlacesAutocomplete('test input');
|
|
|
|
expect(result).toEqual({
|
|
predictions: [
|
|
{
|
|
placeId: 'ChIJ123',
|
|
description: 'Test Location, City, State',
|
|
types: ['establishment'],
|
|
mainText: 'Test Location',
|
|
secondaryText: 'City, State'
|
|
},
|
|
{
|
|
placeId: 'ChIJ456',
|
|
description: 'Another Place',
|
|
types: ['locality'],
|
|
mainText: 'Another Place',
|
|
secondaryText: ''
|
|
}
|
|
]
|
|
});
|
|
});
|
|
|
|
it('should handle predictions without secondary text', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
predictions: [
|
|
{
|
|
place_id: 'ChIJ123',
|
|
description: 'Test Location',
|
|
types: ['establishment'],
|
|
structured_formatting: {
|
|
main_text: 'Test Location'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
mockPlaceAutocomplete.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlacesAutocomplete('test input');
|
|
|
|
expect(result.predictions[0].secondaryText).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('Error responses', () => {
|
|
it('should handle API error responses', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'ZERO_RESULTS',
|
|
error_message: 'No results found'
|
|
}
|
|
};
|
|
|
|
mockPlaceAutocomplete.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlacesAutocomplete('test input');
|
|
|
|
expect(result).toEqual({
|
|
predictions: [],
|
|
error: 'No results found for this query',
|
|
status: 'ZERO_RESULTS'
|
|
});
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Places Autocomplete API error:',
|
|
'ZERO_RESULTS',
|
|
'No results found'
|
|
);
|
|
});
|
|
|
|
it('should handle unknown error status', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'UNKNOWN_STATUS'
|
|
}
|
|
};
|
|
|
|
mockPlaceAutocomplete.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlacesAutocomplete('test input');
|
|
|
|
expect(result.error).toBe('Google Maps API error: UNKNOWN_STATUS');
|
|
});
|
|
|
|
it('should handle network errors', async () => {
|
|
mockPlaceAutocomplete.mockRejectedValue(new Error('Network error'));
|
|
|
|
await expect(service.getPlacesAutocomplete('test input')).rejects.toThrow('Failed to fetch place predictions');
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Places Autocomplete service error:', 'Network error');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getPlaceDetails', () => {
|
|
beforeEach(() => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
service = require('../../../services/googleMapsService');
|
|
});
|
|
|
|
describe('Input validation', () => {
|
|
it('should throw error when API key is not configured', async () => {
|
|
service.apiKey = null;
|
|
|
|
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow('Google Maps API key not configured');
|
|
});
|
|
|
|
it('should throw error when placeId is not provided', async () => {
|
|
await expect(service.getPlaceDetails()).rejects.toThrow('Place ID is required');
|
|
await expect(service.getPlaceDetails('')).rejects.toThrow('Place ID is required');
|
|
await expect(service.getPlaceDetails(null)).rejects.toThrow('Place ID is required');
|
|
});
|
|
});
|
|
|
|
describe('Parameters handling', () => {
|
|
beforeEach(() => {
|
|
mockPlaceDetails.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
result: {
|
|
place_id: 'ChIJ123',
|
|
formatted_address: 'Test Address',
|
|
address_components: [],
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should use default parameters', async () => {
|
|
await service.getPlaceDetails('ChIJ123');
|
|
|
|
expect(mockPlaceDetails).toHaveBeenCalledWith({
|
|
params: {
|
|
key: 'test-api-key',
|
|
place_id: 'ChIJ123',
|
|
fields: [
|
|
'address_components',
|
|
'formatted_address',
|
|
'geometry',
|
|
'place_id'
|
|
],
|
|
language: 'en'
|
|
},
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should accept custom language', async () => {
|
|
await service.getPlaceDetails('ChIJ123', { language: 'fr' });
|
|
|
|
expect(mockPlaceDetails).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
language: 'fr'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should include session token when provided', async () => {
|
|
await service.getPlaceDetails('ChIJ123', { sessionToken: 'session-123' });
|
|
|
|
expect(mockPlaceDetails).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
sessiontoken: 'session-123'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Successful responses', () => {
|
|
it('should return formatted place details', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
result: {
|
|
place_id: 'ChIJ123',
|
|
formatted_address: '123 Test St, Test City, TC 12345, USA',
|
|
address_components: [
|
|
{
|
|
long_name: '123',
|
|
short_name: '123',
|
|
types: ['street_number']
|
|
},
|
|
{
|
|
long_name: 'Test Street',
|
|
short_name: 'Test St',
|
|
types: ['route']
|
|
},
|
|
{
|
|
long_name: 'Test City',
|
|
short_name: 'Test City',
|
|
types: ['locality', 'political']
|
|
},
|
|
{
|
|
long_name: 'Test State',
|
|
short_name: 'TS',
|
|
types: ['administrative_area_level_1', 'political']
|
|
},
|
|
{
|
|
long_name: '12345',
|
|
short_name: '12345',
|
|
types: ['postal_code']
|
|
},
|
|
{
|
|
long_name: 'United States',
|
|
short_name: 'US',
|
|
types: ['country', 'political']
|
|
}
|
|
],
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
mockPlaceDetails.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlaceDetails('ChIJ123');
|
|
|
|
expect(result).toEqual({
|
|
placeId: 'ChIJ123',
|
|
formattedAddress: '123 Test St, Test City, TC 12345, USA',
|
|
addressComponents: {
|
|
streetNumber: '123',
|
|
route: 'Test Street',
|
|
locality: 'Test City',
|
|
administrativeAreaLevel1: 'TS',
|
|
administrativeAreaLevel1Long: 'Test State',
|
|
postalCode: '12345',
|
|
country: 'US'
|
|
},
|
|
geometry: {
|
|
latitude: 40.7128,
|
|
longitude: -74.0060
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should handle place details without address components', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
result: {
|
|
place_id: 'ChIJ123',
|
|
formatted_address: 'Test Address',
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
mockPlaceDetails.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlaceDetails('ChIJ123');
|
|
|
|
expect(result.addressComponents).toEqual({});
|
|
});
|
|
|
|
it('should handle place details without geometry', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
result: {
|
|
place_id: 'ChIJ123',
|
|
formatted_address: 'Test Address'
|
|
}
|
|
}
|
|
};
|
|
|
|
mockPlaceDetails.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlaceDetails('ChIJ123');
|
|
|
|
expect(result.geometry).toEqual({
|
|
latitude: 0,
|
|
longitude: 0
|
|
});
|
|
});
|
|
|
|
it('should handle partial geometry data', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
result: {
|
|
place_id: 'ChIJ123',
|
|
formatted_address: 'Test Address',
|
|
geometry: {}
|
|
}
|
|
}
|
|
};
|
|
|
|
mockPlaceDetails.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.getPlaceDetails('ChIJ123');
|
|
|
|
expect(result.geometry).toEqual({
|
|
latitude: 0,
|
|
longitude: 0
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Error responses', () => {
|
|
it('should handle API error responses', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'NOT_FOUND',
|
|
error_message: 'Place not found'
|
|
}
|
|
};
|
|
|
|
mockPlaceDetails.mockResolvedValue(mockResponse);
|
|
|
|
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow('The specified place was not found');
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Place Details API error:',
|
|
'NOT_FOUND',
|
|
'Place not found'
|
|
);
|
|
});
|
|
|
|
it('should handle response without result', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK'
|
|
}
|
|
};
|
|
|
|
mockPlaceDetails.mockResolvedValue(mockResponse);
|
|
|
|
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow('Google Maps API error: OK');
|
|
});
|
|
|
|
it('should handle network errors', async () => {
|
|
const originalError = new Error('Network error');
|
|
mockPlaceDetails.mockRejectedValue(originalError);
|
|
|
|
await expect(service.getPlaceDetails('ChIJ123')).rejects.toThrow(originalError);
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Place Details service error:', 'Network error');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('geocodeAddress', () => {
|
|
beforeEach(() => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
service = require('../../../services/googleMapsService');
|
|
});
|
|
|
|
describe('Input validation', () => {
|
|
it('should throw error when API key is not configured', async () => {
|
|
service.apiKey = null;
|
|
|
|
await expect(service.geocodeAddress('123 Test St')).rejects.toThrow('Google Maps API key not configured');
|
|
});
|
|
|
|
it('should throw error when address is not provided', async () => {
|
|
await expect(service.geocodeAddress()).rejects.toThrow('Address is required for geocoding');
|
|
await expect(service.geocodeAddress('')).rejects.toThrow('Address is required for geocoding');
|
|
await expect(service.geocodeAddress(' ')).rejects.toThrow('Address is required for geocoding');
|
|
});
|
|
});
|
|
|
|
describe('Parameters handling', () => {
|
|
beforeEach(() => {
|
|
mockGeocode.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
results: [
|
|
{
|
|
formatted_address: 'Test Address',
|
|
place_id: 'ChIJ123',
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should use default parameters', async () => {
|
|
await service.geocodeAddress('123 Test St');
|
|
|
|
expect(mockGeocode).toHaveBeenCalledWith({
|
|
params: {
|
|
key: 'test-api-key',
|
|
address: '123 Test St',
|
|
language: 'en'
|
|
},
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should trim address input', async () => {
|
|
await service.geocodeAddress(' 123 Test St ');
|
|
|
|
expect(mockGeocode).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
address: '123 Test St'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should accept custom language', async () => {
|
|
await service.geocodeAddress('123 Test St', { language: 'fr' });
|
|
|
|
expect(mockGeocode).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
language: 'fr'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should handle component restrictions', async () => {
|
|
const options = {
|
|
componentRestrictions: {
|
|
country: 'us',
|
|
administrative_area: 'CA'
|
|
}
|
|
};
|
|
|
|
await service.geocodeAddress('123 Test St', options);
|
|
|
|
expect(mockGeocode).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
components: 'country:us|administrative_area:CA'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
|
|
it('should handle bounds parameter', async () => {
|
|
const options = {
|
|
bounds: '40.7,-74.1|40.8,-73.9'
|
|
};
|
|
|
|
await service.geocodeAddress('123 Test St', options);
|
|
|
|
expect(mockGeocode).toHaveBeenCalledWith({
|
|
params: expect.objectContaining({
|
|
bounds: '40.7,-74.1|40.8,-73.9'
|
|
}),
|
|
timeout: 5000
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Successful responses', () => {
|
|
it('should return geocoded location', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
results: [
|
|
{
|
|
formatted_address: '123 Test St, Test City, TC 12345, USA',
|
|
place_id: 'ChIJ123',
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
mockGeocode.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.geocodeAddress('123 Test St');
|
|
|
|
expect(result).toEqual({
|
|
latitude: 40.7128,
|
|
longitude: -74.0060,
|
|
formattedAddress: '123 Test St, Test City, TC 12345, USA',
|
|
placeId: 'ChIJ123'
|
|
});
|
|
});
|
|
|
|
it('should return first result when multiple results', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
results: [
|
|
{
|
|
formatted_address: 'First Result',
|
|
place_id: 'ChIJ123',
|
|
geometry: { location: { lat: 40.7128, lng: -74.0060 } }
|
|
},
|
|
{
|
|
formatted_address: 'Second Result',
|
|
place_id: 'ChIJ456',
|
|
geometry: { location: { lat: 40.7129, lng: -74.0061 } }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
mockGeocode.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.geocodeAddress('123 Test St');
|
|
|
|
expect(result.formattedAddress).toBe('First Result');
|
|
expect(result.placeId).toBe('ChIJ123');
|
|
});
|
|
});
|
|
|
|
describe('Error responses', () => {
|
|
it('should handle API error responses', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'ZERO_RESULTS',
|
|
error_message: 'No results found'
|
|
}
|
|
};
|
|
|
|
mockGeocode.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.geocodeAddress('123 Test St');
|
|
|
|
expect(result).toEqual({
|
|
error: 'No results found for this query',
|
|
status: 'ZERO_RESULTS'
|
|
});
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Geocoding API error:',
|
|
'ZERO_RESULTS',
|
|
'No results found'
|
|
);
|
|
});
|
|
|
|
it('should handle empty results array', async () => {
|
|
const mockResponse = {
|
|
data: {
|
|
status: 'OK',
|
|
results: []
|
|
}
|
|
};
|
|
|
|
mockGeocode.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.geocodeAddress('123 Test St');
|
|
|
|
expect(result.error).toBe('Google Maps API error: OK');
|
|
});
|
|
|
|
it('should handle network errors', async () => {
|
|
mockGeocode.mockRejectedValue(new Error('Network error'));
|
|
|
|
await expect(service.geocodeAddress('123 Test St')).rejects.toThrow('Failed to geocode address');
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Geocoding service error:', 'Network error');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getErrorMessage', () => {
|
|
beforeEach(() => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
service = require('../../../services/googleMapsService');
|
|
});
|
|
|
|
it('should return correct error messages for known status codes', () => {
|
|
expect(service.getErrorMessage('ZERO_RESULTS')).toBe('No results found for this query');
|
|
expect(service.getErrorMessage('OVER_QUERY_LIMIT')).toBe('API quota exceeded. Please try again later');
|
|
expect(service.getErrorMessage('REQUEST_DENIED')).toBe('API request denied. Check API key configuration');
|
|
expect(service.getErrorMessage('INVALID_REQUEST')).toBe('Invalid request parameters');
|
|
expect(service.getErrorMessage('UNKNOWN_ERROR')).toBe('Server error. Please try again');
|
|
expect(service.getErrorMessage('NOT_FOUND')).toBe('The specified place was not found');
|
|
});
|
|
|
|
it('should return generic error message for unknown status codes', () => {
|
|
expect(service.getErrorMessage('UNKNOWN_STATUS')).toBe('Google Maps API error: UNKNOWN_STATUS');
|
|
expect(service.getErrorMessage('CUSTOM_ERROR')).toBe('Google Maps API error: CUSTOM_ERROR');
|
|
});
|
|
|
|
it('should handle null/undefined status', () => {
|
|
expect(service.getErrorMessage(null)).toBe('Google Maps API error: null');
|
|
expect(service.getErrorMessage(undefined)).toBe('Google Maps API error: undefined');
|
|
});
|
|
});
|
|
|
|
describe('isConfigured', () => {
|
|
it('should return true when API key is configured', () => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
service = require('../../../services/googleMapsService');
|
|
|
|
expect(service.isConfigured()).toBe(true);
|
|
});
|
|
|
|
it('should return false when API key is not configured', () => {
|
|
delete process.env.GOOGLE_MAPS_API_KEY;
|
|
service = require('../../../services/googleMapsService');
|
|
|
|
expect(service.isConfigured()).toBe(false);
|
|
});
|
|
|
|
it('should return false when API key is empty string', () => {
|
|
process.env.GOOGLE_MAPS_API_KEY = '';
|
|
service = require('../../../services/googleMapsService');
|
|
|
|
expect(service.isConfigured()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Singleton pattern', () => {
|
|
it('should return the same instance on multiple requires', () => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
|
|
const service1 = require('../../../services/googleMapsService');
|
|
const service2 = require('../../../services/googleMapsService');
|
|
|
|
expect(service1).toBe(service2);
|
|
});
|
|
});
|
|
|
|
describe('Integration scenarios', () => {
|
|
beforeEach(() => {
|
|
process.env.GOOGLE_MAPS_API_KEY = 'test-api-key';
|
|
service = require('../../../services/googleMapsService');
|
|
});
|
|
|
|
it('should handle typical place search workflow', async () => {
|
|
// Mock autocomplete response
|
|
mockPlaceAutocomplete.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
predictions: [
|
|
{
|
|
place_id: 'ChIJ123',
|
|
description: 'Test Location',
|
|
types: ['establishment'],
|
|
structured_formatting: {
|
|
main_text: 'Test Location',
|
|
secondary_text: 'City, State'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
// Mock place details response
|
|
mockPlaceDetails.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
result: {
|
|
place_id: 'ChIJ123',
|
|
formatted_address: 'Test Location, City, State',
|
|
address_components: [],
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Step 1: Get autocomplete predictions
|
|
const autocompleteResult = await service.getPlacesAutocomplete('test loc');
|
|
expect(autocompleteResult.predictions).toHaveLength(1);
|
|
|
|
// Step 2: Get detailed place information
|
|
const placeId = autocompleteResult.predictions[0].placeId;
|
|
const detailsResult = await service.getPlaceDetails(placeId);
|
|
|
|
expect(detailsResult.placeId).toBe('ChIJ123');
|
|
expect(detailsResult.geometry.latitude).toBe(40.7128);
|
|
expect(detailsResult.geometry.longitude).toBe(-74.0060);
|
|
});
|
|
|
|
it('should handle geocoding workflow', async () => {
|
|
mockGeocode.mockResolvedValue({
|
|
data: {
|
|
status: 'OK',
|
|
results: [
|
|
{
|
|
formatted_address: '123 Test St, Test City, TC 12345, USA',
|
|
place_id: 'ChIJ123',
|
|
geometry: {
|
|
location: { lat: 40.7128, lng: -74.0060 }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
});
|
|
|
|
const result = await service.geocodeAddress('123 Test St, Test City, TC');
|
|
|
|
expect(result.latitude).toBe(40.7128);
|
|
expect(result.longitude).toBe(-74.0060);
|
|
expect(result.formattedAddress).toBe('123 Test St, Test City, TC 12345, USA');
|
|
});
|
|
});
|
|
}); |