// // LocationServiceTests.swift // AutoCatCoreTests // // Created by Selim Mustafaev on 01.08.2024. // Copyright © 2024 Selim Mustafaev. All rights reserved. // import Testing import CoreLocation import Mockable import Intents import Contacts @testable import AutoCatCore @MainActor struct LocationServiceTests { let latitude: CLLocationDegrees = 10 let longitude: CLLocationDegrees = 10 let address = "Test Address" let location: CLLocation let geocoderMock = MockGeocoderProtocol() let locationManagerMock = MockSwiftLocationProtocol() let settingsServiceMock = MockSettingsServiceProtocol() let locationService: LocationService init() { self.location = CLLocation(latitude: latitude, longitude: longitude) self.locationService = LocationService(geocoder: geocoderMock, locationManager: locationManagerMock, settingsService: settingsServiceMock) given(settingsServiceMock) .user.willReturn(User()) } @Test func getValidAddress() async throws { let placemark = CLPlacemark(location: location, name: address, postalAddress: nil) given(geocoderMock) .reverseGeocodeLocation(.any) .willReturn([placemark]) let result = try await locationService.getAddressForLocation(latitude: latitude, longitude: longitude) verify(geocoderMock) .reverseGeocodeLocation(.any) .called(.once) #expect(result == address) } @Test func getNilAddress() async throws { let placemark = CLPlacemark(location: location, name: nil, postalAddress: nil) given(geocoderMock) .reverseGeocodeLocation(.any) .willReturn([placemark]) await #expect(throws: LocationError.reverseGeocode) { _ = try await locationService.getAddressForLocation(latitude: latitude, longitude: longitude) } verify(geocoderMock) .reverseGeocodeLocation(.any) .called(.once) } @Test func addressNotFound() async throws { given(geocoderMock) .reverseGeocodeLocation(.any) .willReturn([]) await #expect(throws: LocationError.reverseGeocode) { _ = try await locationService.getAddressForLocation(latitude: latitude, longitude: longitude) } verify(geocoderMock) .reverseGeocodeLocation(.any) .called(.once) } @Test("Get location: denied") func getLocationDenied() async throws { given(locationManagerMock) .authorizationStatus .willReturn(.denied) await #expect(throws: CLError(.denied)) { _ = try await locationService.requestCurrentLocation() } } @Test("Get location: not determined -> denied") func getLocationNotDeterminedDenied() async throws { given(locationManagerMock) .authorizationStatus .willReturn(.notDetermined) given(locationManagerMock) .requestPermission(.value(.always)) .willReturn(.denied) when(locationManagerMock) .requestPermission(.value(.always)) .perform { given(locationManagerMock) .authorizationStatus .willReturn(.denied) } await #expect(throws: CLError(.denied)) { _ = try await locationService.requestCurrentLocation() } } @Test("Get location: not determined -> allow") func getLocationNotDeterminedAllow() async throws { given(locationManagerMock) .authorizationStatus .willReturn(.notDetermined) given(locationManagerMock) .requestPermission(.value(.always)) .willReturn(.authorizedWhenInUse) when(locationManagerMock) .requestPermission(.value(.always)) .perform { given(locationManagerMock) .authorizationStatus .willReturn(.authorizedWhenInUse) } given(locationManagerMock) .requestLocation(accuracy: .any, timeout: .any) .willReturn(.didUpdateLocations([location])) let event = try await locationService.requestCurrentLocation() #expect(event.latitude == latitude) #expect(event.longitude == longitude) } @Test("Get location: normal") func getLocationNormal() async throws { given(locationManagerMock) .authorizationStatus .willReturn(.authorizedWhenInUse) given(locationManagerMock) .requestLocation(accuracy: .any, timeout: .any) .willReturn(.didUpdateLocations([location])) let event = try await locationService.requestCurrentLocation() #expect(event.latitude == latitude) #expect(event.longitude == longitude) } @Test("Get location: no location") func getLocationNone() async throws { given(locationManagerMock) .authorizationStatus .willReturn(.authorizedWhenInUse) given(locationManagerMock) .requestLocation(accuracy: .any, timeout: .any) .willReturn(.didUpdateLocations([])) await #expect(throws: LocationError.generic) { _ = try await locationService.requestCurrentLocation() } } @Test("Get location: parallel requests") func getLocationParallel() async throws { given(locationManagerMock) .authorizationStatus .willReturn(.authorizedWhenInUse) given(locationManagerMock) .requestLocation(accuracy: .any, timeout: .any) .willReturn(.didUpdateLocations([location])) async let task1 = locationService.requestCurrentLocation() async let task2 = locationService.requestCurrentLocation() try await Task.sleep(nanoseconds: 500_000_000) async let task3 = locationService.requestCurrentLocation() let (event1, event2, event3) = try await (task1, task2, task3) verify(locationManagerMock) .requestLocation(accuracy: .any, timeout: .any) .called(.exactly(2)) #expect(event1.latitude == latitude) #expect(event1.longitude == longitude) #expect(event2.latitude == latitude) #expect(event2.longitude == longitude) #expect(event3.latitude == latitude) #expect(event3.longitude == longitude) } }