// // 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.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) } @Test("Check (from audio record)") func checkFromAudioRecord() async throws { let vehicle: VehicleDto = .normal let vehicleWithEvent = vehicle.addEvent(.valid) given(storageServiceMock) .loadVehicle(number: .any) .willReturn(.normal) given(apiServiceMock) .checkVehicle(by: .any, notes: .any, events: .value([.valid]), force: .any) .willReturn(vehicleWithEvent) given(locationServiceMock) .resetLastEvent() .willReturn() given(storageServiceMock) .updateVehicle(dto: .any, policy: .any) .willReturn(true) let result = try await vehicleService.checkRecord(number: vehicle.number, event: .valid) verify(apiServiceMock) .checkVehicle(by: .any, notes: .any, events: .value([.valid]), force: .value(false)) .called(.once) verify(storageServiceMock) .updateVehicle(dto: .value(vehicleWithEvent), 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) } }