Adding initial LocationService (reverse geocoding) and its tests

This commit is contained in:
Selim Mustafaev 2024-08-01 19:19:08 +03:00
parent e3cf3aa7cf
commit 299ee23992
7 changed files with 195 additions and 2 deletions

View File

@ -64,6 +64,9 @@
7A5D84C22C1AE5C900C2209B /* VehicleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D84C12C1AE5C900C2209B /* VehicleModel.swift */; };
7A5D84C42C1AE65C00C2209B /* VehicleBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D84C32C1AE65C00C2209B /* VehicleBrand.swift */; };
7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5D84C52C1AE72E00C2209B /* VehicleName.swift */; };
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A60D24C2C5A9D4900D13F7B /* LocationService.swift */; };
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A60D24E2C5A9DA800D13F7B /* LocationServiceProtocol.swift */; };
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A60D2502C5A9E4200D13F7B /* GeocoderProtocol.swift */; };
7A61FF8B2575A2CD00D905D5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7A61FF892575A2CD00D905D5 /* Localizable.strings */; };
7A61FF912575A5B300D905D5 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7A61FF8F2575A5B300D905D5 /* InfoPlist.strings */; };
7A61FFA0257D3CFC00D905D5 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 7A61FFA2257D3CFC00D905D5 /* Localizable.stringsdict */; };
@ -301,6 +304,9 @@
7A5D84C12C1AE5C900C2209B /* VehicleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleModel.swift; sourceTree = "<group>"; };
7A5D84C32C1AE65C00C2209B /* VehicleBrand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleBrand.swift; sourceTree = "<group>"; };
7A5D84C52C1AE72E00C2209B /* VehicleName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleName.swift; sourceTree = "<group>"; };
7A60D24C2C5A9D4900D13F7B /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = "<group>"; };
7A60D24E2C5A9DA800D13F7B /* LocationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServiceProtocol.swift; sourceTree = "<group>"; };
7A60D2502C5A9E4200D13F7B /* GeocoderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeocoderProtocol.swift; sourceTree = "<group>"; };
7A61FF8325759DE700D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
7A61FF8A2575A2CD00D905D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
7A61FF8D2575A2F900D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -657,6 +663,7 @@
7A45FB362C2706D000618694 /* Services */ = {
isa = PBXGroup;
children = (
7A60D24B2C5A9D2700D13F7B /* LocationService */,
7AB5873D2C42FF4000FA7B66 /* ApiService */,
7AB587302C42D35900FA7B66 /* StorageService */,
);
@ -705,6 +712,16 @@
path = Realm;
sourceTree = "<group>";
};
7A60D24B2C5A9D2700D13F7B /* LocationService */ = {
isa = PBXGroup;
children = (
7A60D24C2C5A9D4900D13F7B /* LocationService.swift */,
7A60D24E2C5A9DA800D13F7B /* LocationServiceProtocol.swift */,
7A60D2502C5A9E4200D13F7B /* GeocoderProtocol.swift */,
);
path = LocationService;
sourceTree = "<group>";
};
7A64A2012C19D99D00284124 /* DTO */ = {
isa = PBXGroup;
children = (
@ -1303,6 +1320,8 @@
7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */,
7A64A2122C19E2A100284124 /* VehicleModelDto.swift in Sources */,
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */,
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */,
7A60D24F2C5A9DA800D13F7B /* LocationServiceProtocol.swift in Sources */,
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
7A64A2032C19DA1000284124 /* VehicleDto.swift in Sources */,
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */,
@ -1310,6 +1329,7 @@
7A5D84BC2C1AD81000C2209B /* VehicleOwnershipPeriod.swift in Sources */,
7A64A2202C19E93500284124 /* VehicleNoteDto.swift in Sources */,
7AF6D21A2677C1680086EA64 /* User.swift in Sources */,
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */,
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
7AF6D2152677C1680086EA64 /* Settings.swift in Sources */,

View File

@ -0,0 +1,16 @@
//
// GeocoderProtocol.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 31.07.2024.
// Copyright © 2024 Selim Mustafaev. All rights reserved.
//
import CoreLocation
public protocol GeocoderProtocol {
func reverseGeocodeLocation(_ location: CLLocation) async throws -> [CLPlacemark]
}
extension CLGeocoder: GeocoderProtocol { }

View File

@ -0,0 +1,31 @@
//
// LocationService.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 31.07.2024.
// Copyright © 2024 Selim Mustafaev. All rights reserved.
//
import CoreLocation
@MainActor
public final class LocationService: LocationServiceProtocol {
private var geocoder: GeocoderProtocol
public init(geocoder: GeocoderProtocol) {
self.geocoder = geocoder
}
public func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String {
let location = CLLocation(latitude: latitude, longitude: longitude)
let placemarks = try await geocoder.reverseGeocodeLocation(location)
if let name = placemarks.first?.name {
return name
} else {
throw LocationError.reverseGeocode
}
}
}

View File

@ -0,0 +1,14 @@
//
// LocationServiceProtocol.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 31.07.2024.
// Copyright © 2024 Selim Mustafaev. All rights reserved.
//
import Foundation
public protocol LocationServiceProtocol {
func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String
}

View File

@ -2,13 +2,13 @@ import Foundation
import CoreLocation
import SwiftLocation
enum LocationError: LocalizedError {
public enum LocationError: LocalizedError {
case generic
case permission
case reverseGeocode
var errorDescription: String? {
public var errorDescription: String? {
switch self {
case .generic: "Location error"
case .permission: "Location permission error"

View File

@ -0,0 +1,61 @@
//
// LocationServiceTests.swift
// AutoCatCoreTests
//
// Created by Selim Mustafaev on 01.08.2024.
// Copyright © 2024 Selim Mustafaev. All rights reserved.
//
import Testing
import CoreLocation
import AutoCatCore
@MainActor
struct LocationServiceTests {
let latitude: CLLocationDegrees = 10
let longitude: CLLocationDegrees = 10
let address = "Test Address"
let geocoder = GeocoderMock()
let locationService: LocationService
init() {
self.locationService = LocationService(geocoder: geocoder)
}
@Test
func getValidAddress() async throws {
geocoder.addLocation(latitude: latitude,
longitude: longitude,
address: address)
let result = try await locationService.getAddressForLocation(latitude: latitude,
longitude: longitude)
#expect(result == address)
}
@Test
func getNilAddress() async throws {
geocoder.addLocation(latitude: latitude,
longitude: longitude,
address: nil)
await #expect(throws: LocationError.reverseGeocode) {
_ = try await locationService.getAddressForLocation(latitude: latitude,
longitude: longitude)
}
}
@Test
func addressNotFound() async throws {
await #expect(throws: LocationError.reverseGeocode) {
_ = try await locationService.getAddressForLocation(latitude: latitude,
longitude: longitude)
}
}
}

View File

@ -0,0 +1,51 @@
//
// GeocoderMock.swift
// AutoCatCoreTests
//
// Created by Selim Mustafaev on 31.07.2024.
// Copyright © 2024 Selim Mustafaev. All rights reserved.
//
import CoreLocation
import AutoCatCore
import Intents
import Contacts
final class GeocoderMock {
struct Location {
let latitude: CLLocationDegrees
let longitude: CLLocationDegrees
let address: String?
}
var locations: [Location] = []
func addLocation(latitude: CLLocationDegrees, longitude: CLLocationDegrees, address: String?) {
locations.append(Location(latitude: latitude,
longitude: longitude,
address: address))
}
}
extension GeocoderMock: GeocoderProtocol {
func reverseGeocodeLocation(_ location: CLLocation) async throws -> [CLPlacemark] {
let first = locations.first {
$0.latitude == location.coordinate.latitude && $0.longitude == location.coordinate.longitude
}
guard let first else {
return []
}
let placemark = CLPlacemark(location: CLLocation(latitude: first.latitude, longitude: first.longitude),
name: first.address,
postalAddress: nil)
return [placemark]
}
}