726 lines
23 KiB
JavaScript
726 lines
23 KiB
JavaScript
const request = require('supertest');
|
|
const express = require('express');
|
|
|
|
// Mock dependencies
|
|
jest.mock('../../../services/googleMapsService', () => ({
|
|
getPlacesAutocomplete: jest.fn(),
|
|
getPlaceDetails: jest.fn(),
|
|
geocodeAddress: jest.fn(),
|
|
isConfigured: jest.fn()
|
|
}));
|
|
|
|
// Mock auth middleware
|
|
jest.mock('../../../middleware/auth', () => ({
|
|
authenticateToken: (req, res, next) => {
|
|
if (req.headers.authorization) {
|
|
req.user = { id: 1 };
|
|
next();
|
|
} else {
|
|
res.status(401).json({ error: 'No token provided' });
|
|
}
|
|
}
|
|
}));
|
|
|
|
// Mock rate limiter middleware
|
|
jest.mock('../../../middleware/rateLimiter', () => ({
|
|
burstProtection: (req, res, next) => next(),
|
|
placesAutocomplete: (req, res, next) => next(),
|
|
placeDetails: (req, res, next) => next(),
|
|
geocoding: (req, res, next) => next()
|
|
}));
|
|
|
|
const googleMapsService = require('../../../services/googleMapsService');
|
|
const mapsRoutes = require('../../../routes/maps');
|
|
|
|
// Set up Express app for testing
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use('/maps', mapsRoutes);
|
|
|
|
describe('Maps Routes', () => {
|
|
let consoleSpy, consoleErrorSpy, consoleLogSpy;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
// Set up console spies
|
|
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
});
|
|
|
|
afterEach(() => {
|
|
consoleSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
consoleLogSpy.mockRestore();
|
|
});
|
|
|
|
describe('Input Validation Middleware', () => {
|
|
it('should trim and validate input length', async () => {
|
|
const longInput = 'a'.repeat(501);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: longInput });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Input too long' });
|
|
});
|
|
|
|
it('should validate place ID format', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: 'invalid@place#id!' });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Invalid place ID format' });
|
|
});
|
|
|
|
it('should validate address length', async () => {
|
|
const longAddress = 'a'.repeat(501);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: longAddress });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Address too long' });
|
|
});
|
|
|
|
it('should allow valid place ID format', async () => {
|
|
googleMapsService.getPlaceDetails.mockResolvedValue({
|
|
result: { name: 'Test Place' }
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: 'ChIJ123abc_DEF' });
|
|
|
|
expect(response.status).toBe(200);
|
|
});
|
|
|
|
it('should trim whitespace from inputs', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({
|
|
predictions: []
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: ' test input ' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(googleMapsService.getPlacesAutocomplete).toHaveBeenCalledWith(
|
|
'test input',
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Error Handling Middleware', () => {
|
|
it('should handle API key configuration errors', async () => {
|
|
const configError = new Error('API key not configured');
|
|
googleMapsService.getPlacesAutocomplete.mockRejectedValue(configError);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(503);
|
|
expect(response.body).toEqual({
|
|
error: 'Maps service temporarily unavailable',
|
|
details: 'Configuration issue'
|
|
});
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'Maps service error:',
|
|
'API key not configured'
|
|
);
|
|
});
|
|
|
|
it('should handle quota exceeded errors', async () => {
|
|
const quotaError = new Error('quota exceeded');
|
|
googleMapsService.getPlacesAutocomplete.mockRejectedValue(quotaError);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(429);
|
|
expect(response.body).toEqual({
|
|
error: 'Service temporarily unavailable due to high demand',
|
|
details: 'Please try again later'
|
|
});
|
|
});
|
|
|
|
it('should handle generic service errors', async () => {
|
|
const serviceError = new Error('Network timeout');
|
|
googleMapsService.getPlacesAutocomplete.mockRejectedValue(serviceError);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(500);
|
|
expect(response.body).toEqual({
|
|
error: 'Failed to process request',
|
|
details: 'Network timeout'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('POST /places/autocomplete', () => {
|
|
const mockPredictions = {
|
|
predictions: [
|
|
{
|
|
description: '123 Main St, New York, NY, USA',
|
|
place_id: 'ChIJ123abc',
|
|
types: ['street_address']
|
|
},
|
|
{
|
|
description: '456 Oak Ave, New York, NY, USA',
|
|
place_id: 'ChIJ456def',
|
|
types: ['street_address']
|
|
}
|
|
]
|
|
};
|
|
|
|
it('should return autocomplete predictions successfully', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue(mockPredictions);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({
|
|
input: '123 Main',
|
|
types: ['address'],
|
|
componentRestrictions: { country: 'us' },
|
|
sessionToken: 'session123'
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual(mockPredictions);
|
|
|
|
expect(googleMapsService.getPlacesAutocomplete).toHaveBeenCalledWith(
|
|
'123 Main',
|
|
{
|
|
types: ['address'],
|
|
componentRestrictions: { country: 'us' },
|
|
sessionToken: 'session123'
|
|
}
|
|
);
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
'Places Autocomplete: user=1, query_length=8, results=2'
|
|
);
|
|
});
|
|
|
|
it('should use default types when not provided', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue(mockPredictions);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(googleMapsService.getPlacesAutocomplete).toHaveBeenCalledWith(
|
|
'test',
|
|
{
|
|
types: ['address'],
|
|
componentRestrictions: undefined,
|
|
sessionToken: undefined
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should return empty predictions for short input', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'a' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({ predictions: [] });
|
|
expect(googleMapsService.getPlacesAutocomplete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return empty predictions for missing input', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({ predictions: [] });
|
|
expect(googleMapsService.getPlacesAutocomplete).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should require authentication', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(response.body).toEqual({ error: 'No token provided' });
|
|
});
|
|
|
|
it('should log request with user ID from authenticated user', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue(mockPredictions);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(200);
|
|
// Should log with user ID
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('user=1')
|
|
);
|
|
});
|
|
|
|
it('should handle empty predictions from service', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({ predictions: [] });
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'nonexistent place' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({ predictions: [] });
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
'Places Autocomplete: user=1, query_length=17, results=0'
|
|
);
|
|
});
|
|
|
|
it('should handle service response without predictions array', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({});
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({});
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
'Places Autocomplete: user=1, query_length=4, results=0'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('POST /places/details', () => {
|
|
const mockPlaceDetails = {
|
|
result: {
|
|
place_id: 'ChIJ123abc',
|
|
name: 'Central Park',
|
|
formatted_address: 'New York, NY 10024, USA',
|
|
geometry: {
|
|
location: { lat: 40.785091, lng: -73.968285 }
|
|
},
|
|
types: ['park', 'point_of_interest']
|
|
}
|
|
};
|
|
|
|
it('should return place details successfully', async () => {
|
|
googleMapsService.getPlaceDetails.mockResolvedValue(mockPlaceDetails);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({
|
|
placeId: 'ChIJ123abc',
|
|
sessionToken: 'session123'
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual(mockPlaceDetails);
|
|
|
|
expect(googleMapsService.getPlaceDetails).toHaveBeenCalledWith(
|
|
'ChIJ123abc',
|
|
{ sessionToken: 'session123' }
|
|
);
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
'Place Details: user=1, placeId=ChIJ123abc...'
|
|
);
|
|
});
|
|
|
|
it('should handle place details without session token', async () => {
|
|
googleMapsService.getPlaceDetails.mockResolvedValue(mockPlaceDetails);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: 'ChIJ123abc' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(googleMapsService.getPlaceDetails).toHaveBeenCalledWith(
|
|
'ChIJ123abc',
|
|
{ sessionToken: undefined }
|
|
);
|
|
});
|
|
|
|
it('should return error for missing place ID', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({});
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Place ID is required' });
|
|
expect(googleMapsService.getPlaceDetails).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return error for empty place ID', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: '' });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Place ID is required' });
|
|
});
|
|
|
|
it('should require authentication', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.send({ placeId: 'ChIJ123abc' });
|
|
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it('should handle very long place IDs in logging', async () => {
|
|
const longPlaceId = 'ChIJ' + 'a'.repeat(100);
|
|
googleMapsService.getPlaceDetails.mockResolvedValue(mockPlaceDetails);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: longPlaceId });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
`Place Details: user=1, placeId=${longPlaceId.substring(0, 10)}...`
|
|
);
|
|
});
|
|
|
|
it('should handle service errors', async () => {
|
|
const serviceError = new Error('Place not found');
|
|
googleMapsService.getPlaceDetails.mockRejectedValue(serviceError);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: 'ChIJ123abc' });
|
|
|
|
expect(response.status).toBe(500);
|
|
expect(response.body).toEqual({
|
|
error: 'Failed to process request',
|
|
details: 'Place not found'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('POST /geocode', () => {
|
|
const mockGeocodeResults = {
|
|
results: [
|
|
{
|
|
formatted_address: '123 Main St, New York, NY 10001, USA',
|
|
geometry: {
|
|
location: { lat: 40.7484405, lng: -73.9856644 }
|
|
},
|
|
place_id: 'ChIJ123abc',
|
|
types: ['street_address']
|
|
}
|
|
]
|
|
};
|
|
|
|
it('should return geocoding results successfully', async () => {
|
|
googleMapsService.geocodeAddress.mockResolvedValue(mockGeocodeResults);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({
|
|
address: '123 Main St, New York, NY',
|
|
componentRestrictions: { country: 'US' }
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual(mockGeocodeResults);
|
|
|
|
expect(googleMapsService.geocodeAddress).toHaveBeenCalledWith(
|
|
'123 Main St, New York, NY',
|
|
{ componentRestrictions: { country: 'US' } }
|
|
);
|
|
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
'Geocoding: user=1, address_length=25'
|
|
);
|
|
});
|
|
|
|
it('should handle geocoding without component restrictions', async () => {
|
|
googleMapsService.geocodeAddress.mockResolvedValue(mockGeocodeResults);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: '123 Main St' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(googleMapsService.geocodeAddress).toHaveBeenCalledWith(
|
|
'123 Main St',
|
|
{ componentRestrictions: undefined }
|
|
);
|
|
});
|
|
|
|
it('should return error for missing address', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({});
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Address is required' });
|
|
expect(googleMapsService.geocodeAddress).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return error for empty address', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: '' });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toEqual({ error: 'Address is required' });
|
|
});
|
|
|
|
it('should require authentication', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.send({ address: '123 Main St' });
|
|
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it('should handle addresses with special characters', async () => {
|
|
googleMapsService.geocodeAddress.mockResolvedValue(mockGeocodeResults);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: '123 Main St, Apt #4B' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(googleMapsService.geocodeAddress).toHaveBeenCalledWith(
|
|
'123 Main St, Apt #4B',
|
|
{ componentRestrictions: undefined }
|
|
);
|
|
});
|
|
|
|
it('should handle service errors', async () => {
|
|
const serviceError = new Error('Invalid address');
|
|
googleMapsService.geocodeAddress.mockRejectedValue(serviceError);
|
|
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: 'invalid address' });
|
|
|
|
expect(response.status).toBe(500);
|
|
expect(response.body).toEqual({
|
|
error: 'Failed to process request',
|
|
details: 'Invalid address'
|
|
});
|
|
});
|
|
|
|
it('should handle empty geocoding results', async () => {
|
|
googleMapsService.geocodeAddress.mockResolvedValue({ results: [] });
|
|
|
|
const response = await request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: 'nonexistent address' });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({ results: [] });
|
|
});
|
|
});
|
|
|
|
describe('GET /health', () => {
|
|
it('should return healthy status when service is configured', async () => {
|
|
googleMapsService.isConfigured.mockReturnValue(true);
|
|
|
|
const response = await request(app)
|
|
.get('/maps/health');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({
|
|
status: 'healthy',
|
|
service: 'Google Maps API Proxy',
|
|
timestamp: expect.any(String),
|
|
configuration: {
|
|
apiKeyConfigured: true
|
|
}
|
|
});
|
|
|
|
// Verify timestamp is a valid ISO string
|
|
expect(new Date(response.body.timestamp).toISOString()).toBe(response.body.timestamp);
|
|
});
|
|
|
|
it('should return unavailable status when service is not configured', async () => {
|
|
googleMapsService.isConfigured.mockReturnValue(false);
|
|
|
|
const response = await request(app)
|
|
.get('/maps/health');
|
|
|
|
expect(response.status).toBe(503);
|
|
expect(response.body).toEqual({
|
|
status: 'unavailable',
|
|
service: 'Google Maps API Proxy',
|
|
timestamp: expect.any(String),
|
|
configuration: {
|
|
apiKeyConfigured: false
|
|
}
|
|
});
|
|
});
|
|
|
|
it('should not require authentication', async () => {
|
|
googleMapsService.isConfigured.mockReturnValue(true);
|
|
|
|
const response = await request(app)
|
|
.get('/maps/health');
|
|
|
|
expect(response.status).toBe(200);
|
|
// Should work without authorization header
|
|
});
|
|
|
|
it('should always return current timestamp', async () => {
|
|
googleMapsService.isConfigured.mockReturnValue(true);
|
|
|
|
const beforeTime = new Date().toISOString();
|
|
const response = await request(app)
|
|
.get('/maps/health');
|
|
const afterTime = new Date().toISOString();
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(new Date(response.body.timestamp).getTime()).toBeGreaterThanOrEqual(new Date(beforeTime).getTime());
|
|
expect(new Date(response.body.timestamp).getTime()).toBeLessThanOrEqual(new Date(afterTime).getTime());
|
|
});
|
|
});
|
|
|
|
describe('Rate Limiting Integration', () => {
|
|
it('should apply burst protection to all endpoints', async () => {
|
|
// This test verifies that rate limiting middleware is applied
|
|
// In a real scenario, we'd test actual rate limiting behavior
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({ predictions: [] });
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test' });
|
|
|
|
expect(response.status).toBe(200);
|
|
// The fact that the request succeeded means rate limiting middleware was applied without blocking
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases and Security', () => {
|
|
it('should handle null input gracefully', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: null });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({ predictions: [] });
|
|
});
|
|
|
|
it('should handle undefined values in request body', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({ predictions: [] });
|
|
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({
|
|
input: 'test',
|
|
types: undefined,
|
|
componentRestrictions: undefined,
|
|
sessionToken: undefined
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(googleMapsService.getPlacesAutocomplete).toHaveBeenCalledWith(
|
|
'test',
|
|
{
|
|
types: ['address'], // Should use default
|
|
componentRestrictions: undefined,
|
|
sessionToken: undefined
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should handle malformed JSON gracefully', async () => {
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.set('Content-Type', 'application/json')
|
|
.send('invalid json');
|
|
|
|
expect(response.status).toBe(400); // Express will handle malformed JSON
|
|
});
|
|
|
|
it('should sanitize input to prevent injection attacks', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({ predictions: [] });
|
|
|
|
const maliciousInput = '<script>alert("xss")</script>';
|
|
const response = await request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: maliciousInput });
|
|
|
|
expect(response.status).toBe(200);
|
|
// Input should be treated as string and passed through
|
|
expect(googleMapsService.getPlacesAutocomplete).toHaveBeenCalledWith(
|
|
maliciousInput,
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
|
|
it('should handle concurrent requests to different endpoints', async () => {
|
|
googleMapsService.getPlacesAutocomplete.mockResolvedValue({ predictions: [] });
|
|
googleMapsService.getPlaceDetails.mockResolvedValue({ result: {} });
|
|
googleMapsService.geocodeAddress.mockResolvedValue({ results: [] });
|
|
|
|
const [response1, response2, response3] = await Promise.all([
|
|
request(app)
|
|
.post('/maps/places/autocomplete')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ input: 'test1' }),
|
|
request(app)
|
|
.post('/maps/places/details')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ placeId: 'ChIJ123abc' }),
|
|
request(app)
|
|
.post('/maps/geocode')
|
|
.set('Authorization', 'Bearer valid_token')
|
|
.send({ address: 'test address' })
|
|
]);
|
|
|
|
expect(response1.status).toBe(200);
|
|
expect(response2.status).toBe(200);
|
|
expect(response3.status).toBe(200);
|
|
});
|
|
});
|
|
}); |