Adding VehicleService implementation and tests
This commit is contained in:
parent
3cc2ef2101
commit
ff82b4b755
@ -64,6 +64,7 @@
|
|||||||
7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4927D42CCE438600851C01 /* OptionalBinding.swift */; };
|
7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4927D42CCE438600851C01 /* OptionalBinding.swift */; };
|
||||||
7A530B7A24001D3300CBFE6E /* CheckController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7924001D3300CBFE6E /* CheckController.swift */; };
|
7A530B7A24001D3300CBFE6E /* CheckController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7924001D3300CBFE6E /* CheckController.swift */; };
|
||||||
7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */; };
|
7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */; };
|
||||||
|
7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */; };
|
||||||
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C352C18AC7F00D47C18 /* ApiError.swift */; };
|
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C352C18AC7F00D47C18 /* ApiError.swift */; };
|
||||||
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */; };
|
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */; };
|
||||||
7A599C3B2C18B36A00D47C18 /* FbVerifyTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */; };
|
7A599C3B2C18B36A00D47C18 /* FbVerifyTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */; };
|
||||||
@ -334,6 +335,7 @@
|
|||||||
7A530B7924001D3300CBFE6E /* CheckController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckController.swift; sourceTree = "<group>"; };
|
7A530B7924001D3300CBFE6E /* CheckController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckController.swift; sourceTree = "<group>"; };
|
||||||
7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = "<group>"; };
|
7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = "<group>"; };
|
||||||
7A530B7F2401803A00CBFE6E /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = "<group>"; };
|
7A530B7F2401803A00CBFE6E /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = "<group>"; };
|
||||||
|
7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DbUpdatePolicy.swift; sourceTree = "<group>"; };
|
||||||
7A599C352C18AC7F00D47C18 /* ApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = "<group>"; };
|
7A599C352C18AC7F00D47C18 /* ApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = "<group>"; };
|
||||||
7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbRefreshTokenModel.swift; sourceTree = "<group>"; };
|
7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbRefreshTokenModel.swift; sourceTree = "<group>"; };
|
||||||
7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbVerifyTokenModel.swift; sourceTree = "<group>"; };
|
7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbVerifyTokenModel.swift; sourceTree = "<group>"; };
|
||||||
@ -468,9 +470,21 @@
|
|||||||
7AFBE8CD2C308B53003C491D /* ACMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACMessageView.swift; sourceTree = "<group>"; };
|
7AFBE8CD2C308B53003C491D /* ACMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACMessageView.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
7A54BFD52D43D7E100176D6D /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Extensions/TestError.swift,
|
||||||
|
"Extensions/VehicleDto+Presets.swift",
|
||||||
|
"Extensions/VehicleEventDto+Presets.swift",
|
||||||
|
);
|
||||||
|
target = 7A2E6FA22C42B3AD00C40DA7 /* AutoCatCoreTests */;
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
7A2E6FA42C42B3AD00C40DA7 /* AutoCatCoreTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = AutoCatCoreTests; sourceTree = "<group>"; };
|
7A2E6FA42C42B3AD00C40DA7 /* AutoCatCoreTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = AutoCatCoreTests; sourceTree = "<group>"; };
|
||||||
7AB587232C42D27F00FA7B66 /* AutoCatTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = AutoCatTests; sourceTree = "<group>"; };
|
7AB587232C42D27F00FA7B66 /* AutoCatTests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (7A54BFD52D43D7E100176D6D /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AutoCatTests; sourceTree = "<group>"; };
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -973,6 +987,7 @@
|
|||||||
7A45FB372C27073700618694 /* StorageService.swift */,
|
7A45FB372C27073700618694 /* StorageService.swift */,
|
||||||
7AB587332C42D3FA00FA7B66 /* StorageService+Notes.swift */,
|
7AB587332C42D3FA00FA7B66 /* StorageService+Notes.swift */,
|
||||||
7AA514DF2D0B75B3001CAC50 /* StorageService+Events.swift */,
|
7AA514DF2D0B75B3001CAC50 /* StorageService+Events.swift */,
|
||||||
|
7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */,
|
||||||
);
|
);
|
||||||
path = StorageService;
|
path = StorageService;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1479,6 +1494,7 @@
|
|||||||
7A6B65B32CFB0DB500AABA6B /* NullifyDate.swift in Sources */,
|
7A6B65B32CFB0DB500AABA6B /* NullifyDate.swift in Sources */,
|
||||||
7A7097C22C9EC139007CFDCA /* ServiceContainer.swift in Sources */,
|
7A7097C22C9EC139007CFDCA /* ServiceContainer.swift in Sources */,
|
||||||
7A7097C62C9EC77A007CFDCA /* ServicePropertyWrapper.swift in Sources */,
|
7A7097C62C9EC77A007CFDCA /* ServicePropertyWrapper.swift in Sources */,
|
||||||
|
7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */,
|
||||||
7A5D84BE2C1AE44700C2209B /* VehiclePhoto.swift in Sources */,
|
7A5D84BE2C1AE44700C2209B /* VehiclePhoto.swift in Sources */,
|
||||||
7A64A2262C1A32C800284124 /* AudioRecordDto.swift in Sources */,
|
7A64A2262C1A32C800284124 /* AudioRecordDto.swift in Sources */,
|
||||||
7A761C09267E8EE40005F28F /* Base64FS.swift in Sources */,
|
7A761C09267E8EE40005F28F /* Base64FS.swift in Sources */,
|
||||||
|
|||||||
@ -43,7 +43,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
let settingsService = SettingsService(defaults: .standard)
|
let settingsService = SettingsService(defaults: .standard)
|
||||||
|
|
||||||
container.register(SettingsServiceProtocol.self, instance: settingsService)
|
container.register(SettingsServiceProtocol.self, instance: settingsService)
|
||||||
container.register(ApiServiceProtocol.self, instance: ApiService())
|
|
||||||
|
let apiService = ApiService()
|
||||||
|
container.register(ApiServiceProtocol.self, instance: apiService)
|
||||||
|
|
||||||
let locationService = LocationService(
|
let locationService = LocationService(
|
||||||
geocoder: CLGeocoder(),
|
geocoder: CLGeocoder(),
|
||||||
@ -53,10 +55,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
|
|
||||||
container.register(LocationServiceProtocol.self, instance: locationService)
|
container.register(LocationServiceProtocol.self, instance: locationService)
|
||||||
|
|
||||||
container.register(StorageServiceProtocol.self,
|
let storageService = try await StorageService(settingsService: settingsService)
|
||||||
instance: try await StorageService(settingsService: settingsService))
|
container.register(StorageServiceProtocol.self, instance: storageService)
|
||||||
|
|
||||||
container.register(VehicleServiceProtocol.self, instance: VehicleService())
|
let vehicleService = VehicleService(apiService: apiService,
|
||||||
|
storageService: storageService,
|
||||||
|
locationService: locationService)
|
||||||
|
container.register(VehicleServiceProtocol.self, instance: vehicleService)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRootController(scene: UIScene) {
|
func setupRootController(scene: UIScene) {
|
||||||
|
|||||||
@ -135,7 +135,7 @@ class EventsViewModel: ACHudContainer {
|
|||||||
await wrapWithToast { [weak self] in
|
await wrapWithToast { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
let vehicle = try await apiOperation()
|
let vehicle = try await apiOperation()
|
||||||
try await storageService.updateVehicleIfExists(dto: vehicle)
|
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
||||||
self.vehicle = vehicle
|
self.vehicle = vehicle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ final class HistoryViewModel: ACHudContainer {
|
|||||||
|
|
||||||
func checkNewNumber(_ number: String) async {
|
func checkNewNumber(_ number: String) async {
|
||||||
await wrapWithToast { [weak self] in
|
await wrapWithToast { [weak self] in
|
||||||
try await self?.vehicleService.checkAndStore(number: number)
|
try await self?.vehicleService.check(number: number)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class NotesViewModel: ACHudContainer {
|
|||||||
await wrapWithToast { [weak self] in
|
await wrapWithToast { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
let vehicle = try await apiOp()
|
let vehicle = try await apiOp()
|
||||||
try await storageService.updateVehicleIfExists(dto: vehicle)
|
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
||||||
self.vehicle = vehicle
|
self.vehicle = vehicle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class ReportViewModel: ACHudContainer {
|
|||||||
func checkGB() async {
|
func checkGB() async {
|
||||||
await wrapWithToast {
|
await wrapWithToast {
|
||||||
self.vehicle = try await self.apiService.checkVehicleGb(by: self.vehicle.getNumber())
|
self.vehicle = try await self.apiService.checkVehicleGb(by: self.vehicle.getNumber())
|
||||||
try await self.storageService.updateVehicleIfExists(dto: self.vehicle)
|
try await self.storageService.updateVehicle(dto: self.vehicle, policy: .ifExists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,14 @@ public struct VehicleDto: Sendable, Equatable {
|
|||||||
public var synchronized: Bool = true
|
public var synchronized: Bool = true
|
||||||
|
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
|
public init(number: String) {
|
||||||
|
|
||||||
|
self.number = number
|
||||||
|
self.addedDate = Date().timeIntervalSince1970
|
||||||
|
self.updatedDate = self.addedDate
|
||||||
|
self.synchronized = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VehicleDto: Identifiable {
|
extension VehicleDto: Identifiable {
|
||||||
|
|||||||
@ -25,5 +25,6 @@ public protocol ApiServiceProtocol: Sendable {
|
|||||||
func getRegions() async throws -> [VehicleRegion]
|
func getRegions() async throws -> [VehicleRegion]
|
||||||
func getYears() async throws -> [Int]
|
func getYears() async throws -> [Int]
|
||||||
|
|
||||||
|
func checkVehicle(by number: String, notes: [VehicleNoteDto], events: [VehicleEventDto], force: Bool) async throws -> VehicleDto
|
||||||
func checkVehicleGb(by number: String) async throws -> VehicleDto
|
func checkVehicleGb(by number: String) async throws -> VehicleDto
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@ public final class LocationService {
|
|||||||
|
|
||||||
private var eventTask: Task<VehicleEventDto,Error>?
|
private var eventTask: Task<VehicleEventDto,Error>?
|
||||||
|
|
||||||
|
private(set) public var lastEvent: VehicleEventDto?
|
||||||
|
|
||||||
public init(geocoder: GeocoderProtocol,
|
public init(geocoder: GeocoderProtocol,
|
||||||
locationManager: SwiftLocationProtocol,
|
locationManager: SwiftLocationProtocol,
|
||||||
settingsService: SettingsServiceProtocol) {
|
settingsService: SettingsServiceProtocol) {
|
||||||
@ -52,6 +54,10 @@ public final class LocationService {
|
|||||||
|
|
||||||
return VehicleEventDto(lat: coordinate.latitude, lon: coordinate.longitude, addedBy: settingsService.user.email)
|
return VehicleEventDto(lat: coordinate.latitude, lon: coordinate.longitude, addedBy: settingsService.user.email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setLastEvent(_ event: VehicleEventDto) {
|
||||||
|
lastEvent = event
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LocationService: LocationServiceProtocol {
|
extension LocationService: LocationServiceProtocol {
|
||||||
@ -77,10 +83,30 @@ extension LocationService: LocationServiceProtocol {
|
|||||||
let task = Task {
|
let task = Task {
|
||||||
let location = try await requestLocation()
|
let location = try await requestLocation()
|
||||||
eventTask = nil
|
eventTask = nil
|
||||||
|
lastEvent = location
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
eventTask = task
|
eventTask = task
|
||||||
return try await task.value
|
return try await task.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getRecentLocation() async throws -> VehicleEventDto {
|
||||||
|
|
||||||
|
var event: VehicleEventDto
|
||||||
|
|
||||||
|
if let lastEvent, Date().timeIntervalSince1970 - lastEvent.date < 100 {
|
||||||
|
event = lastEvent
|
||||||
|
} else {
|
||||||
|
event = try await requestCurrentLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
event.address = try? await getAddressForLocation(latitude: event.latitude, longitude: event.longitude)
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
public func resetLastEvent() {
|
||||||
|
|
||||||
|
lastEvent = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,16 @@
|
|||||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Mockable
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public protocol LocationServiceProtocol {
|
@Mockable
|
||||||
|
public protocol LocationServiceProtocol: Sendable {
|
||||||
|
|
||||||
|
var lastEvent: VehicleEventDto? { get }
|
||||||
|
|
||||||
func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String
|
func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String
|
||||||
func requestCurrentLocation() async throws -> VehicleEventDto
|
func requestCurrentLocation() async throws -> VehicleEventDto
|
||||||
|
func getRecentLocation() async throws -> VehicleEventDto
|
||||||
|
func resetLastEvent()
|
||||||
}
|
}
|
||||||
|
|||||||
13
AutoCatCore/Services/StorageService/DbUpdatePolicy.swift
Normal file
13
AutoCatCore/Services/StorageService/DbUpdatePolicy.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// DbUpdatePolicy.swift
|
||||||
|
// AutoCatCore
|
||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 24.01.2025.
|
||||||
|
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
public enum DbUpdatePolicy: CaseIterable {
|
||||||
|
|
||||||
|
case always
|
||||||
|
case ifExists
|
||||||
|
}
|
||||||
@ -43,15 +43,25 @@ public actor StorageService: StorageServiceProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateVehicleIfExists(dto: VehicleDto) async throws {
|
@discardableResult
|
||||||
|
public func updateVehicle(dto: VehicleDto, policy: DbUpdatePolicy) async throws -> Bool {
|
||||||
|
|
||||||
guard realm.object(ofType: Vehicle.self, forPrimaryKey: dto.getNumber()) != nil else {
|
let shouldUpdate = switch policy {
|
||||||
return
|
case .always:
|
||||||
|
true
|
||||||
|
case .ifExists:
|
||||||
|
realm.object(ofType: Vehicle.self, forPrimaryKey: dto.getNumber()) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard shouldUpdate else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
try await realm.asyncWrite {
|
try await realm.asyncWrite {
|
||||||
realm.add(Vehicle(dto: dto), update: .all)
|
realm.add(Vehicle(dto: dto), update: .all)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func loadVehicle(number: String) async throws -> VehicleDto {
|
public func loadVehicle(number: String) async throws -> VehicleDto {
|
||||||
|
|||||||
@ -18,7 +18,8 @@ public protocol StorageServiceProtocol: Sendable {
|
|||||||
// Vehicles
|
// Vehicles
|
||||||
func loadVehicles() async -> [VehicleDto]
|
func loadVehicles() async -> [VehicleDto]
|
||||||
func loadVehicle(number: String) async throws -> VehicleDto
|
func loadVehicle(number: String) async throws -> VehicleDto
|
||||||
func updateVehicleIfExists(dto: VehicleDto) async throws
|
@discardableResult
|
||||||
|
func updateVehicle(dto: VehicleDto, policy: DbUpdatePolicy) async throws -> Bool
|
||||||
|
|
||||||
// Notes
|
// Notes
|
||||||
func addNote(text: String, to number: String) async throws -> VehicleDto
|
func addNote(text: String, to number: String) async throws -> VehicleDto
|
||||||
|
|||||||
@ -6,19 +6,82 @@
|
|||||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
public struct VehicleWithErrors: Sendable {
|
||||||
|
|
||||||
|
public var vehicle: VehicleDto
|
||||||
|
public var errors: [Error]
|
||||||
|
}
|
||||||
|
|
||||||
public final class VehicleService {
|
public final class VehicleService {
|
||||||
|
|
||||||
public init() {}
|
let apiService: ApiServiceProtocol
|
||||||
|
let storageService: StorageServiceProtocol
|
||||||
|
let locationService: LocationServiceProtocol
|
||||||
|
|
||||||
|
public init(apiService: ApiServiceProtocol,
|
||||||
|
storageService: StorageServiceProtocol,
|
||||||
|
locationService: LocationServiceProtocol) {
|
||||||
|
|
||||||
|
self.apiService = apiService
|
||||||
|
self.storageService = storageService
|
||||||
|
self.locationService = locationService
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VehicleService: VehicleServiceProtocol {
|
extension VehicleService: VehicleServiceProtocol {
|
||||||
|
|
||||||
public func check(number: String) async throws -> VehicleDto {
|
func check(number: String,
|
||||||
|
forceUpdate: Bool,
|
||||||
|
trackLocation: Bool,
|
||||||
|
dbUpdatePolicy: DbUpdatePolicy) async throws -> VehicleWithErrors {
|
||||||
|
|
||||||
VehicleDto()
|
var vehicle = (try? await storageService.loadVehicle(number: number)) ?? VehicleDto(number: number)
|
||||||
|
var errors: [Error] = []
|
||||||
|
|
||||||
|
let events = vehicle.events
|
||||||
|
let notes = vehicle.notes
|
||||||
|
|
||||||
|
async let locationTask = trackLocation ? locationService.getRecentLocation() : nil
|
||||||
|
async let vehicleTask = apiService.checkVehicle(by: number, notes: notes, events: events, force: forceUpdate)
|
||||||
|
|
||||||
|
do {
|
||||||
|
vehicle = try await vehicleTask
|
||||||
|
} catch {
|
||||||
|
errors.append(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func checkAndStore(number: String) async throws {
|
if trackLocation {
|
||||||
|
do {
|
||||||
|
if let event = try await locationTask {
|
||||||
|
vehicle.events.append(event)
|
||||||
|
vehicle.synchronized = false
|
||||||
|
if !vehicle.unrecognized {
|
||||||
|
vehicle = try await apiService.add(event: event, to: number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await locationService.resetLastEvent()
|
||||||
|
try await storageService.updateVehicle(dto: vehicle, policy: dbUpdatePolicy)
|
||||||
|
|
||||||
|
return VehicleWithErrors(vehicle: vehicle, errors: errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func check(number: String) async throws -> VehicleWithErrors {
|
||||||
|
|
||||||
|
try await check(number: number, forceUpdate: false, trackLocation: true, dbUpdatePolicy: .always)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateHistory(number: String) async throws -> VehicleWithErrors {
|
||||||
|
|
||||||
|
try await check(number: number, forceUpdate: true, trackLocation: false, dbUpdatePolicy: .always)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateSearch(number: String) async throws -> VehicleWithErrors {
|
||||||
|
|
||||||
|
try await check(number: number, forceUpdate: true, trackLocation: false, dbUpdatePolicy: .ifExists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
public protocol VehicleServiceProtocol: Sendable {
|
public protocol VehicleServiceProtocol: Sendable {
|
||||||
|
|
||||||
func check(number: String) async throws -> VehicleDto
|
func check(number: String) async throws -> VehicleWithErrors
|
||||||
func checkAndStore(number: String) async throws
|
func updateHistory(number: String) async throws -> VehicleWithErrors
|
||||||
|
func updateSearch(number: String) async throws -> VehicleWithErrors
|
||||||
}
|
}
|
||||||
|
|||||||
@ -174,6 +174,7 @@ struct LocationServiceTests {
|
|||||||
|
|
||||||
#expect(event.latitude == latitude)
|
#expect(event.latitude == latitude)
|
||||||
#expect(event.longitude == longitude)
|
#expect(event.longitude == longitude)
|
||||||
|
#expect(locationService.lastEvent != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Get location: no location")
|
@Test("Get location: no location")
|
||||||
@ -190,6 +191,8 @@ struct LocationServiceTests {
|
|||||||
await #expect(throws: LocationError.generic) {
|
await #expect(throws: LocationError.generic) {
|
||||||
_ = try await locationService.requestCurrentLocation()
|
_ = try await locationService.requestCurrentLocation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#expect(locationService.lastEvent == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Get location: parallel requests")
|
@Test("Get location: parallel requests")
|
||||||
@ -222,4 +225,106 @@ struct LocationServiceTests {
|
|||||||
#expect(event3.latitude == latitude)
|
#expect(event3.latitude == latitude)
|
||||||
#expect(event3.longitude == longitude)
|
#expect(event3.longitude == longitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test("Get recent location (request new)", arguments: [true, false])
|
||||||
|
func getRecentLocationNew(hasAddress: Bool) async throws {
|
||||||
|
|
||||||
|
let placemark = CLPlacemark(location: location, name: address, postalAddress: nil)
|
||||||
|
|
||||||
|
given(locationManagerMock)
|
||||||
|
.authorizationStatus
|
||||||
|
.willReturn(.authorizedWhenInUse)
|
||||||
|
|
||||||
|
given(locationManagerMock)
|
||||||
|
.requestLocation(accuracy: .any, timeout: .any)
|
||||||
|
.willReturn(.didUpdateLocations([location]))
|
||||||
|
|
||||||
|
given(geocoderMock)
|
||||||
|
.reverseGeocodeLocation(.any)
|
||||||
|
.willReturn(hasAddress ? [placemark] : [])
|
||||||
|
|
||||||
|
let event = try await locationService.getRecentLocation()
|
||||||
|
|
||||||
|
verify(locationManagerMock)
|
||||||
|
.requestLocation(accuracy: .any, timeout: .any)
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
#expect(event.latitude == latitude)
|
||||||
|
#expect(event.longitude == longitude)
|
||||||
|
#expect(locationService.lastEvent != nil)
|
||||||
|
#expect(event.address == (hasAddress ? address : nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Get recent location (return existing)", arguments: [true, false])
|
||||||
|
func getRecentLocationExisting(hasAddress: Bool) async throws {
|
||||||
|
|
||||||
|
let placemark = CLPlacemark(location: location, name: address, postalAddress: nil)
|
||||||
|
|
||||||
|
given(locationManagerMock)
|
||||||
|
.authorizationStatus
|
||||||
|
.willReturn(.authorizedWhenInUse)
|
||||||
|
|
||||||
|
given(geocoderMock)
|
||||||
|
.reverseGeocodeLocation(.any)
|
||||||
|
.willReturn(hasAddress ? [placemark] : [])
|
||||||
|
|
||||||
|
locationService.setLastEvent(VehicleEventDto(lat: latitude, lon: longitude, addedBy: nil))
|
||||||
|
|
||||||
|
let event = try await locationService.getRecentLocation()
|
||||||
|
|
||||||
|
verify(locationManagerMock)
|
||||||
|
.requestLocation(accuracy: .any, timeout: .any)
|
||||||
|
.called(.never)
|
||||||
|
|
||||||
|
#expect(event.latitude == latitude)
|
||||||
|
#expect(event.longitude == longitude)
|
||||||
|
#expect(locationService.lastEvent != nil)
|
||||||
|
#expect(event.address == (hasAddress ? address : nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Get recent location (update existing)", arguments: [true, false])
|
||||||
|
func getRecentLocationUpdateExisting(hasAddress: Bool) async throws {
|
||||||
|
|
||||||
|
let placemark = CLPlacemark(location: location, name: address, postalAddress: nil)
|
||||||
|
|
||||||
|
given(locationManagerMock)
|
||||||
|
.authorizationStatus
|
||||||
|
.willReturn(.authorizedWhenInUse)
|
||||||
|
|
||||||
|
given(locationManagerMock)
|
||||||
|
.requestLocation(accuracy: .any, timeout: .any)
|
||||||
|
.willReturn(.didUpdateLocations([location]))
|
||||||
|
|
||||||
|
given(geocoderMock)
|
||||||
|
.reverseGeocodeLocation(.any)
|
||||||
|
.willReturn(hasAddress ? [placemark] : [])
|
||||||
|
|
||||||
|
// Set existing date which is too old and should not be used
|
||||||
|
var existingEvent = VehicleEventDto(lat: 0, lon: 0, addedBy: nil)
|
||||||
|
existingEvent.date = try #require(Calendar.current.date(byAdding: .day, value: -1, to: Date())).timeIntervalSince1970
|
||||||
|
locationService.setLastEvent(existingEvent)
|
||||||
|
|
||||||
|
let event = try await locationService.getRecentLocation()
|
||||||
|
|
||||||
|
verify(locationManagerMock)
|
||||||
|
.requestLocation(accuracy: .any, timeout: .any)
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
#expect(event.latitude == latitude)
|
||||||
|
#expect(event.longitude == longitude)
|
||||||
|
#expect(locationService.lastEvent != nil)
|
||||||
|
#expect(event.address == (hasAddress ? address : nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Reset last event")
|
||||||
|
func resetLastEvent() {
|
||||||
|
|
||||||
|
locationService.setLastEvent(VehicleEventDto(lat: latitude, lon: longitude, addedBy: nil))
|
||||||
|
|
||||||
|
locationService.resetLastEvent()
|
||||||
|
|
||||||
|
#expect(locationService.lastEvent == nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,4 +64,29 @@ struct StorageServiceTests {
|
|||||||
_ = try await storageService.loadVehicle(number: nonExistingVehicleNumber)
|
_ = try await storageService.loadVehicle(number: nonExistingVehicleNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("Update existing vehicle", arguments: DbUpdatePolicy.allCases)
|
||||||
|
func updateVehicle(policy: DbUpdatePolicy) async throws {
|
||||||
|
|
||||||
|
let vehicle = VehicleDto(number: existingVehicleNumber)
|
||||||
|
|
||||||
|
let updated = try await storageService.updateVehicle(dto: vehicle, policy: policy)
|
||||||
|
|
||||||
|
#expect(updated == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Update non-existent vehicle", arguments: DbUpdatePolicy.allCases)
|
||||||
|
func updateNonExistentVehicle(policy: DbUpdatePolicy) async throws {
|
||||||
|
|
||||||
|
let vehicle = VehicleDto(number: nonExistingVehicleNumber)
|
||||||
|
|
||||||
|
let updated = try await storageService.updateVehicle(dto: vehicle, policy: policy)
|
||||||
|
|
||||||
|
switch policy {
|
||||||
|
case .always:
|
||||||
|
#expect(updated == true)
|
||||||
|
case .ifExists:
|
||||||
|
#expect(updated == false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
424
AutoCatCoreTests/VehicleServiceTests.swift
Normal file
424
AutoCatCoreTests/VehicleServiceTests.swift
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
//
|
||||||
|
// VehicleServiceTests.swift
|
||||||
|
// AutoCatCoreTests
|
||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 24.01.2025.
|
||||||
|
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Testing
|
||||||
|
import Mockable
|
||||||
|
import AutoCatCore
|
||||||
|
|
||||||
|
struct VehicleServiceTests {
|
||||||
|
|
||||||
|
let apiServiceMock = MockApiServiceProtocol()
|
||||||
|
let storageServiceMock = MockStorageServiceProtocol()
|
||||||
|
let locationServiceMock: MockLocationServiceProtocol
|
||||||
|
|
||||||
|
let vehicleService: VehicleServiceProtocol
|
||||||
|
|
||||||
|
let existingVehicleNumber = "А123АА761"
|
||||||
|
let nonExistingVehicleNumber = "А999АА761"
|
||||||
|
let testVin = "1234567890"
|
||||||
|
|
||||||
|
init () async throws {
|
||||||
|
|
||||||
|
self.locationServiceMock = await .init()
|
||||||
|
|
||||||
|
self.vehicleService = VehicleService(apiService: apiServiceMock,
|
||||||
|
storageService: storageServiceMock,
|
||||||
|
locationService: locationServiceMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (all throws)")
|
||||||
|
func checkVehicleAllThrows() async throws {
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
await #expect(throws: TestError.generic) {
|
||||||
|
_ = try await vehicleService.check(number: existingVehicleNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (all but DB update throws)")
|
||||||
|
func checkVehicleDbUpdate() async throws {
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: existingVehicleNumber)
|
||||||
|
|
||||||
|
#expect(result.vehicle.unrecognized)
|
||||||
|
#expect(result.vehicle.events.isEmpty)
|
||||||
|
#expect(result.errors.count == 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (existing unrecognized vehicle)")
|
||||||
|
func checkVehicleExistingVehicle() async throws {
|
||||||
|
|
||||||
|
var vehicle = VehicleDto(number: existingVehicleNumber)
|
||||||
|
vehicle.vin1 = testVin
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(vehicle)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: existingVehicleNumber)
|
||||||
|
|
||||||
|
#expect(result.vehicle.vin1 == testVin)
|
||||||
|
#expect(result.errors.count == 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (location received)")
|
||||||
|
func checkVehicleLocationReceived() async throws {
|
||||||
|
|
||||||
|
var vehicle = VehicleDto(number: existingVehicleNumber)
|
||||||
|
vehicle.vin1 = testVin
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(vehicle)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: VehicleDto.validNumber)
|
||||||
|
|
||||||
|
#expect(result.vehicle.vin1 == testVin)
|
||||||
|
#expect(result.errors.count == 1)
|
||||||
|
#expect(result.vehicle.events.count == 1)
|
||||||
|
#expect(result.vehicle.events.first?.latitude == VehicleEventDto.validLatitude)
|
||||||
|
#expect(result.vehicle.events.first?.longitude == VehicleEventDto.validLongitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (existing normal vehicle)")
|
||||||
|
func checkVehicleExistingNormalVehicle() async throws {
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: VehicleDto.validNumber)
|
||||||
|
|
||||||
|
#expect(result.vehicle.number == VehicleDto.validNumber)
|
||||||
|
#expect(result.errors.count == 2)
|
||||||
|
#expect(result.vehicle.events.count == 1)
|
||||||
|
#expect(result.vehicle.events.first?.latitude == VehicleEventDto.validLatitude)
|
||||||
|
#expect(result.vehicle.events.first?.longitude == VehicleEventDto.validLongitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (existing normal vehicle, add event)")
|
||||||
|
func checkVehicleExistingNormalVehicleAddEvent() async throws {
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willThrow(TestError.generic)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.willReturn(.normal.addEvent(.valid))
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: VehicleDto.validNumber)
|
||||||
|
|
||||||
|
#expect(result.vehicle.number == VehicleDto.validNumber)
|
||||||
|
#expect(result.errors.count == 1)
|
||||||
|
#expect(result.vehicle.events.count == 1)
|
||||||
|
#expect(result.vehicle.events.first?.latitude == VehicleEventDto.validLatitude)
|
||||||
|
#expect(result.vehicle.events.first?.longitude == VehicleEventDto.validLongitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check vehicle (with server check working)")
|
||||||
|
func checkVehicleServerCheck() async throws {
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.willReturn(.normal.addEvent(.valid))
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: VehicleDto.validNumber)
|
||||||
|
|
||||||
|
#expect(result.vehicle.number == VehicleDto.validNumber)
|
||||||
|
#expect(result.errors.count == 0)
|
||||||
|
#expect(result.vehicle.events.count == 1)
|
||||||
|
#expect(result.vehicle.events.first?.latitude == VehicleEventDto.validLatitude)
|
||||||
|
#expect(result.vehicle.events.first?.longitude == VehicleEventDto.validLongitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Check")
|
||||||
|
func check() async throws {
|
||||||
|
|
||||||
|
let vehicle: VehicleDto = .normal
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willReturn(vehicle)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.willReturn(vehicle.addEvent(.valid))
|
||||||
|
|
||||||
|
let result = try await vehicleService.check(number: vehicle.number)
|
||||||
|
|
||||||
|
verify(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .value(false))
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
verify(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
verify(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
verify(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .value(.always))
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
#expect(result.vehicle.number == vehicle.number)
|
||||||
|
#expect(result.errors.count == 0)
|
||||||
|
#expect(result.vehicle.events.count == 1)
|
||||||
|
#expect(result.vehicle.events.first?.latitude == VehicleEventDto.validLatitude)
|
||||||
|
#expect(result.vehicle.events.first?.longitude == VehicleEventDto.validLongitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Update (history)")
|
||||||
|
func updateHistory() async throws {
|
||||||
|
|
||||||
|
let vehicle: VehicleDto = .normal
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willReturn(vehicle)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.willReturn(vehicle.addEvent(.valid))
|
||||||
|
|
||||||
|
let result = try await vehicleService.updateHistory(number: vehicle.number)
|
||||||
|
|
||||||
|
verify(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .value(true))
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
verify(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.called(.never)
|
||||||
|
|
||||||
|
verify(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.called(.never)
|
||||||
|
|
||||||
|
verify(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .value(.always))
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
#expect(result.vehicle.number == vehicle.number)
|
||||||
|
#expect(result.errors.count == 0)
|
||||||
|
#expect(result.vehicle.events.count == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Update (search)")
|
||||||
|
func updateSearch() async throws {
|
||||||
|
|
||||||
|
let vehicle: VehicleDto = .normal
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.loadVehicle(number: .any)
|
||||||
|
.willReturn(.normal)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.willReturn(.valid)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .any)
|
||||||
|
.willReturn(vehicle)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.resetLastEvent()
|
||||||
|
.willReturn()
|
||||||
|
|
||||||
|
given(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
|
.willReturn(true)
|
||||||
|
|
||||||
|
given(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.willReturn(vehicle.addEvent(.valid))
|
||||||
|
|
||||||
|
let result = try await vehicleService.updateSearch(number: vehicle.number)
|
||||||
|
|
||||||
|
verify(apiServiceMock)
|
||||||
|
.checkVehicle(by: .any, notes: .any, events: .any, force: .value(true))
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
verify(locationServiceMock)
|
||||||
|
.getRecentLocation()
|
||||||
|
.called(.never)
|
||||||
|
|
||||||
|
verify(apiServiceMock)
|
||||||
|
.add(event: .any, to: .any)
|
||||||
|
.called(.never)
|
||||||
|
|
||||||
|
verify(storageServiceMock)
|
||||||
|
.updateVehicle(dto: .any, policy: .value(.ifExists))
|
||||||
|
.called(.once)
|
||||||
|
|
||||||
|
#expect(result.vehicle.number == vehicle.number)
|
||||||
|
#expect(result.errors.count == 0)
|
||||||
|
#expect(result.vehicle.events.count == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,7 +21,7 @@ struct EventsTests {
|
|||||||
var viewModel: EventsViewModel
|
var viewModel: EventsViewModel
|
||||||
|
|
||||||
lazy var vehicleWithEvent: VehicleDto = .normal.addEvent(.valid)
|
lazy var vehicleWithEvent: VehicleDto = .normal.addEvent(.valid)
|
||||||
lazy var unrecognizedVehicleWithEvent: VehicleDto = .unrecognized.addEvent(.valid)
|
lazy var unrecognizedVehicleWithEvent: VehicleDto = .unrecognizedVehicle.addEvent(.valid)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
||||||
@ -56,14 +56,14 @@ struct EventsTests {
|
|||||||
.willReturn(vehicleWithEvent)
|
.willReturn(vehicleWithEvent)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .value(updatedVehicle))
|
.updateVehicle(dto: .value(updatedVehicle), policy: .value(.ifExists))
|
||||||
.willReturn()
|
.willReturn(true)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.add(event: .value(.valid), to: .value(VehicleDto.validNumber))
|
.add(event: .value(.valid), to: .value(VehicleDto.validNumber))
|
||||||
.willReturn(unrecognizedVehicleWithEvent)
|
.willReturn(unrecognizedVehicleWithEvent)
|
||||||
|
|
||||||
viewModel = makeViewModel(vehicle: isUnrecognized ? .unrecognized : .normal)
|
viewModel = makeViewModel(vehicle: isUnrecognized ? .unrecognizedVehicle : .normal)
|
||||||
await viewModel.addEvent(.valid)
|
await viewModel.addEvent(.valid)
|
||||||
|
|
||||||
verify(apiServiceMock)
|
verify(apiServiceMock)
|
||||||
@ -71,7 +71,7 @@ struct EventsTests {
|
|||||||
.called(isUnrecognized ? .never : .once)
|
.called(isUnrecognized ? .never : .once)
|
||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .any)
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
.called(isUnrecognized ? .never : .once)
|
.called(isUnrecognized ? .never : .once)
|
||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
@ -89,19 +89,19 @@ struct EventsTests {
|
|||||||
mutating func deleteEvent(isUnrecognized: Bool) async throws {
|
mutating func deleteEvent(isUnrecognized: Bool) async throws {
|
||||||
|
|
||||||
let vehicleWithEvent: VehicleDto = isUnrecognized ? unrecognizedVehicleWithEvent : vehicleWithEvent
|
let vehicleWithEvent: VehicleDto = isUnrecognized ? unrecognizedVehicleWithEvent : vehicleWithEvent
|
||||||
let vehicle: VehicleDto = isUnrecognized ? .unrecognized : .normal
|
let vehicle: VehicleDto = isUnrecognized ? .unrecognizedVehicle : .normal
|
||||||
|
|
||||||
given(apiServiceMock)
|
given(apiServiceMock)
|
||||||
.remove(event: .value(VehicleEventDto.validId))
|
.remove(event: .value(VehicleEventDto.validId))
|
||||||
.willReturn(.normal)
|
.willReturn(.normal)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .value(vehicle))
|
.updateVehicle(dto: .value(vehicle), policy: .value(.ifExists))
|
||||||
.willReturn()
|
.willReturn(true)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.remove(event: .value(VehicleEventDto.validId), from: .value(VehicleDto.validNumber))
|
.remove(event: .value(VehicleEventDto.validId), from: .value(VehicleDto.validNumber))
|
||||||
.willReturn(.unrecognized)
|
.willReturn(.unrecognizedVehicle)
|
||||||
|
|
||||||
viewModel = makeViewModel(vehicle: vehicleWithEvent)
|
viewModel = makeViewModel(vehicle: vehicleWithEvent)
|
||||||
await viewModel.deleteEvent(VehicleEventDto.valid.viewModel)
|
await viewModel.deleteEvent(VehicleEventDto.valid.viewModel)
|
||||||
@ -111,7 +111,7 @@ struct EventsTests {
|
|||||||
.called(isUnrecognized ? .never : .once)
|
.called(isUnrecognized ? .never : .once)
|
||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .any)
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
.called(isUnrecognized ? .never : .once)
|
.called(isUnrecognized ? .never : .once)
|
||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
|
|||||||
@ -18,3 +18,10 @@ enum TestError: LocalizedError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Error {
|
||||||
|
|
||||||
|
static var testError: Error {
|
||||||
|
return TestError.generic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import AutoCatCore
|
|||||||
extension VehicleDto {
|
extension VehicleDto {
|
||||||
|
|
||||||
static let validNumber: String = "А123АА761"
|
static let validNumber: String = "А123АА761"
|
||||||
|
static let validNumber2: String = "А456АА761"
|
||||||
|
|
||||||
static var normal: VehicleDto {
|
static var normal: VehicleDto {
|
||||||
var vehicle = VehicleDto()
|
var vehicle = VehicleDto()
|
||||||
@ -19,7 +20,14 @@ extension VehicleDto {
|
|||||||
return vehicle
|
return vehicle
|
||||||
}
|
}
|
||||||
|
|
||||||
static var unrecognized: VehicleDto {
|
static var normal2: VehicleDto {
|
||||||
|
var vehicle = VehicleDto()
|
||||||
|
vehicle.number = validNumber2
|
||||||
|
vehicle.brand = VehicleBrandDto()
|
||||||
|
return vehicle
|
||||||
|
}
|
||||||
|
|
||||||
|
static var unrecognizedVehicle: VehicleDto {
|
||||||
var vehicle = VehicleDto()
|
var vehicle = VehicleDto()
|
||||||
vehicle.number = validNumber
|
vehicle.number = validNumber
|
||||||
return vehicle
|
return vehicle
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import AutoCatCore
|
import AutoCatCore
|
||||||
@testable import AutoCat
|
|
||||||
|
|
||||||
extension VehicleEventDto {
|
extension VehicleEventDto {
|
||||||
|
|
||||||
@ -34,15 +33,4 @@ extension VehicleEventDto {
|
|||||||
event.address = testAddress
|
event.address = testAddress
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewModel: EventModel {
|
|
||||||
|
|
||||||
EventModel(
|
|
||||||
id: id,
|
|
||||||
date: "",
|
|
||||||
coordinate: CLLocationCoordinate2DMake(latitude, longitude),
|
|
||||||
address: address ?? "",
|
|
||||||
isMe: true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
AutoCatTests/Extensions/VehicleEventDto+ViewModel.swift
Normal file
25
AutoCatTests/Extensions/VehicleEventDto+ViewModel.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// VehicleEventDto+ViewModel.swift
|
||||||
|
// AutoCatTests
|
||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 25.01.2025.
|
||||||
|
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
@testable import AutoCat
|
||||||
|
import AutoCatCore
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
|
extension VehicleEventDto {
|
||||||
|
|
||||||
|
var viewModel: EventModel {
|
||||||
|
|
||||||
|
EventModel(
|
||||||
|
id: id,
|
||||||
|
date: "",
|
||||||
|
coordinate: CLLocationCoordinate2DMake(latitude, longitude),
|
||||||
|
address: address ?? "",
|
||||||
|
isMe: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,7 +23,7 @@ final class NotesTests {
|
|||||||
let noteTextModified = "Test note text modified"
|
let noteTextModified = "Test note text modified"
|
||||||
|
|
||||||
lazy var vehicleWithNote: VehicleDto = .normal.addNote(text: noteText)
|
lazy var vehicleWithNote: VehicleDto = .normal.addNote(text: noteText)
|
||||||
lazy var unrecognizedVehicleWithNote: VehicleDto = .unrecognized.addNote(text: noteText)
|
lazy var unrecognizedVehicleWithNote: VehicleDto = .unrecognizedVehicle.addNote(text: noteText)
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
storageServiceMock = MockStorageServiceProtocol()
|
storageServiceMock = MockStorageServiceProtocol()
|
||||||
@ -44,8 +44,8 @@ final class NotesTests {
|
|||||||
.willReturn(vehicleWithNote)
|
.willReturn(vehicleWithNote)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .any)
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
.willReturn()
|
.willReturn(true)
|
||||||
|
|
||||||
viewModel.vehicle = .normal
|
viewModel.vehicle = .normal
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.addNote(text: .any, to: .any).called(.never)
|
.addNote(text: .any, to: .any).called(.never)
|
||||||
.updateVehicleIfExists(dto: .any).called(.once)
|
.updateVehicle(dto: .any, policy: .any).called(.once)
|
||||||
|
|
||||||
#expect(viewModel.vehicle.notes.contains { $0.text == noteText })
|
#expect(viewModel.vehicle.notes.contains { $0.text == noteText })
|
||||||
#expect(viewModel.hud == nil)
|
#expect(viewModel.hud == nil)
|
||||||
@ -69,7 +69,7 @@ final class NotesTests {
|
|||||||
.addNote(text: .any, to: .any)
|
.addNote(text: .any, to: .any)
|
||||||
.willReturn(vehicleWithNote)
|
.willReturn(vehicleWithNote)
|
||||||
|
|
||||||
viewModel.vehicle = .unrecognized
|
viewModel.vehicle = .unrecognizedVehicle
|
||||||
|
|
||||||
await viewModel.addNote(text: noteText)
|
await viewModel.addNote(text: noteText)
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.addNote(text: .any, to: .any).called(.once)
|
.addNote(text: .any, to: .any).called(.once)
|
||||||
.updateVehicleIfExists(dto: .any).called(.never)
|
.updateVehicle(dto: .any, policy: .any).called(.never)
|
||||||
|
|
||||||
#expect(viewModel.vehicle.notes.contains { $0.text == noteText })
|
#expect(viewModel.vehicle.notes.contains { $0.text == noteText })
|
||||||
#expect(viewModel.hud == nil)
|
#expect(viewModel.hud == nil)
|
||||||
@ -95,8 +95,8 @@ final class NotesTests {
|
|||||||
.willReturn(modifiedVehicle)
|
.willReturn(modifiedVehicle)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .any)
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
.willReturn()
|
.willReturn(true)
|
||||||
|
|
||||||
viewModel.vehicle = vehicleWithNote
|
viewModel.vehicle = vehicleWithNote
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.editNote(id: .any, text: .any, for: .any).called(.never)
|
.editNote(id: .any, text: .any, for: .any).called(.never)
|
||||||
.updateVehicleIfExists(dto: .any).called(.once)
|
.updateVehicle(dto: .any, policy: .any).called(.once)
|
||||||
|
|
||||||
#expect(viewModel.vehicle.notes.contains { $0.text == noteTextModified })
|
#expect(viewModel.vehicle.notes.contains { $0.text == noteTextModified })
|
||||||
#expect(viewModel.hud == nil)
|
#expect(viewModel.hud == nil)
|
||||||
@ -116,12 +116,12 @@ final class NotesTests {
|
|||||||
@Test("Edit note (unrecognized vehicle)")
|
@Test("Edit note (unrecognized vehicle)")
|
||||||
func editNoteUnrecognized() async throws {
|
func editNoteUnrecognized() async throws {
|
||||||
|
|
||||||
let vehicle: VehicleDto = .unrecognized.addNote(text: noteText)
|
let vehicle: VehicleDto = .unrecognizedVehicle.addNote(text: noteText)
|
||||||
let noteId = try #require(vehicle.notes.first?.id)
|
let noteId = try #require(vehicle.notes.first?.id)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.editNote(id: .value(noteId), text: .value(noteTextModified), for: .any)
|
.editNote(id: .value(noteId), text: .value(noteTextModified), for: .any)
|
||||||
.willReturn(.unrecognized.addNote(text: noteTextModified, id: noteId))
|
.willReturn(.unrecognizedVehicle.addNote(text: noteTextModified, id: noteId))
|
||||||
|
|
||||||
viewModel.vehicle = vehicle
|
viewModel.vehicle = vehicle
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.editNote(id: .any, text: .any, for: .any).called(.once)
|
.editNote(id: .any, text: .any, for: .any).called(.once)
|
||||||
.updateVehicleIfExists(dto: .any).called(.never)
|
.updateVehicle(dto: .any, policy: .any).called(.never)
|
||||||
|
|
||||||
#expect(viewModel.vehicle.notes.contains { $0.text == noteTextModified })
|
#expect(viewModel.vehicle.notes.contains { $0.text == noteTextModified })
|
||||||
#expect(viewModel.hud == nil)
|
#expect(viewModel.hud == nil)
|
||||||
@ -148,8 +148,8 @@ final class NotesTests {
|
|||||||
.willReturn(.normal)
|
.willReturn(.normal)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .any)
|
.updateVehicle(dto: .any, policy: .any)
|
||||||
.willReturn()
|
.willReturn(true)
|
||||||
|
|
||||||
viewModel.vehicle = vehicleWithNote
|
viewModel.vehicle = vehicleWithNote
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.deleteNote(id: .any, for: .any).called(.never)
|
.deleteNote(id: .any, for: .any).called(.never)
|
||||||
.updateVehicleIfExists(dto: .any).called(.once)
|
.updateVehicle(dto: .any, policy: .any).called(.once)
|
||||||
|
|
||||||
#expect(!viewModel.vehicle.notes.contains { $0.text == noteText })
|
#expect(!viewModel.vehicle.notes.contains { $0.text == noteText })
|
||||||
#expect(viewModel.hud == nil)
|
#expect(viewModel.hud == nil)
|
||||||
@ -173,7 +173,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.deleteNote(id: .value(noteId), for: .any)
|
.deleteNote(id: .value(noteId), for: .any)
|
||||||
.willReturn(.unrecognized)
|
.willReturn(.unrecognizedVehicle)
|
||||||
|
|
||||||
viewModel.vehicle = unrecognizedVehicleWithNote
|
viewModel.vehicle = unrecognizedVehicleWithNote
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ final class NotesTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.deleteNote(id: .value(noteId), for: .any).called(.once)
|
.deleteNote(id: .value(noteId), for: .any).called(.once)
|
||||||
.updateVehicleIfExists(dto: .any).called(.never)
|
.updateVehicle(dto: .any, policy: .any).called(.never)
|
||||||
|
|
||||||
#expect(!viewModel.vehicle.notes.contains { $0.text == noteText })
|
#expect(!viewModel.vehicle.notes.contains { $0.text == noteText })
|
||||||
#expect(viewModel.hud == nil)
|
#expect(viewModel.hud == nil)
|
||||||
|
|||||||
@ -110,8 +110,8 @@ class ReportTests {
|
|||||||
.willReturn(updatedVehicle)
|
.willReturn(updatedVehicle)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .value(updatedVehicle))
|
.updateVehicle(dto: .value(updatedVehicle), policy: .value(.ifExists))
|
||||||
.willReturn()
|
.willReturn(true)
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.loadVehicle(number: .value(existingVehicleNumber))
|
.loadVehicle(number: .value(existingVehicleNumber))
|
||||||
@ -125,7 +125,7 @@ class ReportTests {
|
|||||||
.called(.once)
|
.called(.once)
|
||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.updateVehicleIfExists(dto: .value(updatedVehicle))
|
.updateVehicle(dto: .value(updatedVehicle), policy: .value(.ifExists))
|
||||||
.called(.once)
|
.called(.once)
|
||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user