backend unit tests
This commit is contained in:
726
backend/tests/unit/routes/maps.test.js
Normal file
726
backend/tests/unit/routes/maps.test.js
Normal file
@@ -0,0 +1,726 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user