185 lines
5.9 KiB
JavaScript
185 lines
5.9 KiB
JavaScript
// Mock dependencies before requiring the service
|
|
jest.mock('../../../models', () => ({
|
|
sequelize: {
|
|
query: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
jest.mock('sequelize', () => ({
|
|
QueryTypes: {
|
|
SELECT: 'SELECT',
|
|
},
|
|
}));
|
|
|
|
jest.mock('../../../utils/logger', () => ({
|
|
info: jest.fn(),
|
|
error: jest.fn(),
|
|
warn: jest.fn(),
|
|
}));
|
|
|
|
const { sequelize } = require('../../../models');
|
|
const locationService = require('../../../services/locationService');
|
|
|
|
describe('LocationService', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('findUsersInRadius', () => {
|
|
it('should find users within specified radius', async () => {
|
|
const mockUsers = [
|
|
{ id: 'user-1', email: 'user1@test.com', firstName: 'User', lastName: 'One', latitude: '37.7749', longitude: '-122.4194', distance: '1.5' },
|
|
{ id: 'user-2', email: 'user2@test.com', firstName: 'User', lastName: 'Two', latitude: '37.7849', longitude: '-122.4094', distance: '2.3' },
|
|
];
|
|
|
|
sequelize.query.mockResolvedValue(mockUsers);
|
|
|
|
const result = await locationService.findUsersInRadius(37.7749, -122.4194, 10);
|
|
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0]).toMatchObject({
|
|
id: 'user-1',
|
|
email: 'user1@test.com',
|
|
firstName: 'User',
|
|
lastName: 'One',
|
|
});
|
|
expect(parseFloat(result[0].distance)).toBeCloseTo(1.5, 1);
|
|
});
|
|
|
|
it('should use default radius of 10 miles', async () => {
|
|
sequelize.query.mockResolvedValue([]);
|
|
|
|
await locationService.findUsersInRadius(37.7749, -122.4194);
|
|
|
|
expect(sequelize.query).toHaveBeenCalledWith(
|
|
expect.any(String),
|
|
expect.objectContaining({
|
|
replacements: expect.objectContaining({
|
|
radiusMiles: 10,
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should throw error when latitude is missing', async () => {
|
|
await expect(locationService.findUsersInRadius(null, -122.4194, 10))
|
|
.rejects.toThrow('Latitude and longitude are required');
|
|
});
|
|
|
|
it('should throw error when longitude is missing', async () => {
|
|
await expect(locationService.findUsersInRadius(37.7749, null, 10))
|
|
.rejects.toThrow('Latitude and longitude are required');
|
|
});
|
|
|
|
it('should throw error when radius is zero', async () => {
|
|
await expect(locationService.findUsersInRadius(37.7749, -122.4194, 0))
|
|
.rejects.toThrow('Radius must be between 1 and 100 miles');
|
|
});
|
|
|
|
it('should throw error when radius is negative', async () => {
|
|
await expect(locationService.findUsersInRadius(37.7749, -122.4194, -5))
|
|
.rejects.toThrow('Radius must be between 1 and 100 miles');
|
|
});
|
|
|
|
it('should throw error when radius exceeds 100 miles', async () => {
|
|
await expect(locationService.findUsersInRadius(37.7749, -122.4194, 150))
|
|
.rejects.toThrow('Radius must be between 1 and 100 miles');
|
|
});
|
|
|
|
it('should handle database errors', async () => {
|
|
sequelize.query.mockRejectedValue(new Error('Database error'));
|
|
|
|
await expect(locationService.findUsersInRadius(37.7749, -122.4194, 10))
|
|
.rejects.toThrow('Failed to find users in radius');
|
|
});
|
|
|
|
it('should return empty array when no users found', async () => {
|
|
sequelize.query.mockResolvedValue([]);
|
|
|
|
const result = await locationService.findUsersInRadius(37.7749, -122.4194, 10);
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('should format distance to 2 decimal places', async () => {
|
|
const mockUsers = [
|
|
{ id: 'user-1', email: 'user1@test.com', firstName: 'User', lastName: 'One', latitude: '37.7749', longitude: '-122.4194', distance: '1.23456789' },
|
|
];
|
|
|
|
sequelize.query.mockResolvedValue(mockUsers);
|
|
|
|
const result = await locationService.findUsersInRadius(37.7749, -122.4194, 10);
|
|
|
|
expect(result[0].distance).toBe('1.23');
|
|
});
|
|
});
|
|
|
|
describe('calculateDistance', () => {
|
|
it('should calculate distance between two points', () => {
|
|
// San Francisco to Los Angeles: approximately 347 miles
|
|
const distance = locationService.calculateDistance(
|
|
37.7749, -122.4194, // San Francisco
|
|
34.0522, -118.2437 // Los Angeles
|
|
);
|
|
|
|
expect(distance).toBeGreaterThan(340);
|
|
expect(distance).toBeLessThan(360);
|
|
});
|
|
|
|
it('should return 0 for same coordinates', () => {
|
|
const distance = locationService.calculateDistance(
|
|
37.7749, -122.4194,
|
|
37.7749, -122.4194
|
|
);
|
|
|
|
expect(distance).toBe(0);
|
|
});
|
|
|
|
it('should calculate short distances accurately', () => {
|
|
// Two points about 1 mile apart
|
|
const distance = locationService.calculateDistance(
|
|
37.7749, -122.4194,
|
|
37.7893, -122.4094
|
|
);
|
|
|
|
expect(distance).toBeGreaterThan(0.5);
|
|
expect(distance).toBeLessThan(2);
|
|
});
|
|
|
|
it('should handle negative coordinates', () => {
|
|
// Sydney, Australia to Melbourne, Australia
|
|
const distance = locationService.calculateDistance(
|
|
-33.8688, 151.2093, // Sydney
|
|
-37.8136, 144.9631 // Melbourne
|
|
);
|
|
|
|
expect(distance).toBeGreaterThan(400);
|
|
expect(distance).toBeLessThan(500);
|
|
});
|
|
|
|
it('should handle crossing the prime meridian', () => {
|
|
// London to Paris
|
|
const distance = locationService.calculateDistance(
|
|
51.5074, -0.1278, // London
|
|
48.8566, 2.3522 // Paris
|
|
);
|
|
|
|
expect(distance).toBeGreaterThan(200);
|
|
expect(distance).toBeLessThan(250);
|
|
});
|
|
});
|
|
|
|
describe('toRadians', () => {
|
|
it('should convert degrees to radians', () => {
|
|
expect(locationService.toRadians(0)).toBe(0);
|
|
expect(locationService.toRadians(180)).toBeCloseTo(Math.PI, 5);
|
|
expect(locationService.toRadians(90)).toBeCloseTo(Math.PI / 2, 5);
|
|
expect(locationService.toRadians(360)).toBeCloseTo(2 * Math.PI, 5);
|
|
});
|
|
|
|
it('should handle negative degrees', () => {
|
|
expect(locationService.toRadians(-90)).toBeCloseTo(-Math.PI / 2, 5);
|
|
});
|
|
});
|
|
});
|