// src/services/googleGeocodingService.server.test.ts import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; // Un-mock the module we are testing to ensure we use the real implementation. vi.unmock('./googleGeocodingService.server'); // Mock the logger to prevent console output and allow for assertions vi.mock('./logger.server', () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }, })); // Import the service to be tested and its mocked dependencies import { GoogleGeocodingService } from './googleGeocodingService.server'; import { logger as mockLogger } from './logger.server'; describe('Google Geocoding Service', () => { let googleGeocodingService: GoogleGeocodingService; beforeEach(() => { vi.clearAllMocks(); // Mock the global fetch function before each test vi.stubGlobal('fetch', vi.fn()); vi.unstubAllEnvs(); googleGeocodingService = new GoogleGeocodingService(); }); afterEach(() => { vi.unstubAllEnvs(); }); it('should return coordinates for a valid address when API key is present', async () => { // Arrange vi.stubEnv('GOOGLE_MAPS_API_KEY', 'test-api-key'); const mockApiResponse = { status: 'OK', results: [ { geometry: { location: { lat: 34.0522, lng: -118.2437 }, }, }, ], }; vi.mocked(fetch).mockResolvedValue({ ok: true, json: async () => mockApiResponse, } as Response); // Act const result = await googleGeocodingService.geocode('Los Angeles, CA', mockLogger); // Assert expect(result).toEqual({ lat: 34.0522, lng: -118.2437 }); expect(fetch).toHaveBeenCalledWith( expect.stringContaining('https://maps.googleapis.com/maps/api/geocode/json'), ); expect(mockLogger.info).toHaveBeenCalledWith( { address: 'Los Angeles, CA', result: { lat: 34.0522, lng: -118.2437 } }, '[GoogleGeocodingService] Successfully geocoded address', ); }); it('should throw an error if GOOGLE_MAPS_API_KEY is not set', async () => { // Arrange vi.stubEnv('GOOGLE_MAPS_API_KEY', ''); // Act & Assert await expect(googleGeocodingService.geocode('Any Address', mockLogger)).rejects.toThrow( 'GOOGLE_MAPS_API_KEY is not set.', ); expect(mockLogger.error).toHaveBeenCalledWith('[GoogleGeocodingService] API key is missing.'); }); it('should return null if the API returns a status other than "OK"', async () => { // Arrange vi.stubEnv('GOOGLE_MAPS_API_KEY', 'test-api-key'); const mockApiResponse = { status: 'ZERO_RESULTS', results: [] }; vi.mocked(fetch).mockResolvedValue({ ok: true, json: async () => mockApiResponse, } as Response); // Act const result = await googleGeocodingService.geocode('Invalid Address', mockLogger); // Assert expect(result).toBeNull(); expect(mockLogger.warn).toHaveBeenCalledWith( { address: 'Invalid Address', status: 'ZERO_RESULTS' }, '[GoogleGeocodingService] Geocoding failed or returned no results.', ); }); it('should throw an error if the fetch response is not ok', async () => { // Arrange vi.stubEnv('GOOGLE_MAPS_API_KEY', 'test-api-key'); vi.mocked(fetch).mockResolvedValue({ ok: false, status: 403, } as Response); // Act & Assert await expect(googleGeocodingService.geocode('Any Address', mockLogger)).rejects.toThrow( 'Google Maps API returned status 403', ); expect(mockLogger.error).toHaveBeenCalledWith( { err: expect.any(Error), address: 'Any Address' }, '[GoogleGeocodingService] An error occurred while calling the Google Maps API.', ); }); it('should throw an error if the fetch call itself fails', async () => { // Arrange vi.stubEnv('GOOGLE_MAPS_API_KEY', 'test-api-key'); const networkError = new Error('Network request failed'); vi.mocked(fetch).mockRejectedValue(networkError); // Act & Assert await expect(googleGeocodingService.geocode('Any Address', mockLogger)).rejects.toThrow( networkError, ); expect(mockLogger.error).toHaveBeenCalledWith( { err: networkError, address: 'Any Address' }, '[GoogleGeocodingService] An error occurred while calling the Google Maps API.', ); }); });