Adding tests for SwiftData storage service

This commit is contained in:
Selim Mustafaev 2025-06-13 18:15:09 +03:00
parent 412d09fe6d
commit e6f9f1d043
31 changed files with 425 additions and 848 deletions

View File

@ -12,11 +12,6 @@
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0B22C707E13005731AC /* SettingsServiceProtocol.swift */; };
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0B42C707E2B005731AC /* SettingsService.swift */; };
7A0818722DF83CF0000219FE /* SDVehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0818712DF83CF0000219FE /* SDVehicle.swift */; };
7A0818742DF83D98000219FE /* SDVehicleBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0818732DF83D98000219FE /* SDVehicleBrand.swift */; };
7A0818762DF83DB4000219FE /* SDVehicleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0818752DF83DB4000219FE /* SDVehicleName.swift */; };
7A0818782DF84AE4000219FE /* SDVehicleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0818772DF84AE4000219FE /* SDVehicleModel.swift */; };
7A08187A2DF84B77000219FE /* SDVehicleEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0818792DF84B77000219FE /* SDVehicleEngine.swift */; };
7A08187E2DF84CE0000219FE /* SDVehiclePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A08187D2DF84CE0000219FE /* SDVehiclePhoto.swift */; };
7A10226C2C551EC500B84627 /* LocationEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226B2C551EC500B84627 /* LocationEditScreen.swift */; };
7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */; };
7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022762C557EC400B84627 /* LocationPickerScreen.swift */; };
@ -164,12 +159,6 @@
7ACBB91E2CB9B155005A5168 /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7ACBB91D2CB9B155005A5168 /* Mockable */; };
7ACBB9202CB9B16C005A5168 /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7ACBB91F2CB9B16C005A5168 /* Mockable */; };
7AD176B02DC127540023049D /* PNButtonType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD176AF2DC127540023049D /* PNButtonType.swift */; };
7AD8571E2DF8700F009E4B72 /* SDVehicleOwnershipPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8571D2DF8700F009E4B72 /* SDVehicleOwnershipPeriod.swift */; };
7AD857202DF874F8009E4B72 /* SDVehicleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8571F2DF874F8009E4B72 /* SDVehicleEvent.swift */; };
7AD857222DF875B2009E4B72 /* SDOsago.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857212DF875B2009E4B72 /* SDOsago.swift */; };
7AD857242DF87637009E4B72 /* SDVehicleAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857232DF87637009E4B72 /* SDVehicleAd.swift */; };
7AD857262DF876C9009E4B72 /* SDVehicleNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857252DF876C9009E4B72 /* SDVehicleNote.swift */; };
7AD857282DF87733009E4B72 /* SDDebugInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857272DF87733009E4B72 /* SDDebugInfo.swift */; };
7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD857292DF87928009E4B72 /* SDAudioRecord.swift */; };
7AD8572D2DF95F72009E4B72 /* SwiftDataStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD8572C2DF95F72009E4B72 /* SwiftDataStorageService.swift */; };
7ADCBC572DB51739002522C0 /* AutoCatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCBC562DB51739002522C0 /* AutoCatApp.swift */; };
@ -271,11 +260,6 @@
7A06E0B22C707E13005731AC /* SettingsServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsServiceProtocol.swift; sourceTree = "<group>"; };
7A06E0B42C707E2B005731AC /* SettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsService.swift; sourceTree = "<group>"; };
7A0818712DF83CF0000219FE /* SDVehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicle.swift; sourceTree = "<group>"; };
7A0818732DF83D98000219FE /* SDVehicleBrand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleBrand.swift; sourceTree = "<group>"; };
7A0818752DF83DB4000219FE /* SDVehicleName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleName.swift; sourceTree = "<group>"; };
7A0818772DF84AE4000219FE /* SDVehicleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleModel.swift; sourceTree = "<group>"; };
7A0818792DF84B77000219FE /* SDVehicleEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleEngine.swift; sourceTree = "<group>"; };
7A08187D2DF84CE0000219FE /* SDVehiclePhoto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehiclePhoto.swift; sourceTree = "<group>"; };
7A10226B2C551EC500B84627 /* LocationEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditScreen.swift; sourceTree = "<group>"; };
7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditViewModel.swift; sourceTree = "<group>"; };
7A1022762C557EC400B84627 /* LocationPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerScreen.swift; sourceTree = "<group>"; };
@ -434,12 +418,6 @@
7AC44B812DB390B900ADC026 /* MainTabScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabScreen.swift; sourceTree = "<group>"; };
7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchTextField+Dumb.swift"; sourceTree = "<group>"; };
7AD176AF2DC127540023049D /* PNButtonType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNButtonType.swift; sourceTree = "<group>"; };
7AD8571D2DF8700F009E4B72 /* SDVehicleOwnershipPeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleOwnershipPeriod.swift; sourceTree = "<group>"; };
7AD8571F2DF874F8009E4B72 /* SDVehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleEvent.swift; sourceTree = "<group>"; };
7AD857212DF875B2009E4B72 /* SDOsago.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDOsago.swift; sourceTree = "<group>"; };
7AD857232DF87637009E4B72 /* SDVehicleAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleAd.swift; sourceTree = "<group>"; };
7AD857252DF876C9009E4B72 /* SDVehicleNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDVehicleNote.swift; sourceTree = "<group>"; };
7AD857272DF87733009E4B72 /* SDDebugInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDDebugInfo.swift; sourceTree = "<group>"; };
7AD857292DF87928009E4B72 /* SDAudioRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDAudioRecord.swift; sourceTree = "<group>"; };
7AD8572C2DF95F72009E4B72 /* SwiftDataStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataStorageService.swift; sourceTree = "<group>"; };
7ADCBC562DB51739002522C0 /* AutoCatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCatApp.swift; sourceTree = "<group>"; };
@ -555,17 +533,6 @@
isa = PBXGroup;
children = (
7A0818712DF83CF0000219FE /* SDVehicle.swift */,
7A0818732DF83D98000219FE /* SDVehicleBrand.swift */,
7A0818752DF83DB4000219FE /* SDVehicleName.swift */,
7A0818772DF84AE4000219FE /* SDVehicleModel.swift */,
7A0818792DF84B77000219FE /* SDVehicleEngine.swift */,
7A08187D2DF84CE0000219FE /* SDVehiclePhoto.swift */,
7AD8571D2DF8700F009E4B72 /* SDVehicleOwnershipPeriod.swift */,
7AD8571F2DF874F8009E4B72 /* SDVehicleEvent.swift */,
7AD857212DF875B2009E4B72 /* SDOsago.swift */,
7AD857232DF87637009E4B72 /* SDVehicleAd.swift */,
7AD857252DF876C9009E4B72 /* SDVehicleNote.swift */,
7AD857272DF87733009E4B72 /* SDDebugInfo.swift */,
7AD857292DF87928009E4B72 /* SDAudioRecord.swift */,
);
path = SwiftData;
@ -1496,7 +1463,6 @@
7ABDA8092D8710F80083C715 /* AutoCancellable.swift in Sources */,
7A5D84C02C1AE4DC00C2209B /* VehicleEngine.swift in Sources */,
7AB0EF812C5CC0FE00291EE6 /* SwiftLocationProtocol.swift in Sources */,
7AD857242DF87637009E4B72 /* SDVehicleAd.swift in Sources */,
7AF6D2282677C2DC0086EA64 /* Constants.swift in Sources */,
7A64A2182C19E64800284124 /* VehicleOwnershipPeriodDto.swift in Sources */,
7ABDA8052D8705210083C715 /* VehicleRecordServiceProtocol.swift in Sources */,
@ -1511,18 +1477,15 @@
7A64A2262C1A32C800284124 /* AudioRecordDto.swift in Sources */,
7A761C09267E8EE40005F28F /* Base64FS.swift in Sources */,
7A64A21E2C19E8D500284124 /* VehicleAdDto.swift in Sources */,
7AD857202DF874F8009E4B72 /* SDVehicleEvent.swift in Sources */,
7AF6D2202677C1680086EA64 /* Filter.swift in Sources */,
7A1AE6552DFB2E9D00ECFC4D /* SwiftDataStorageService+AudioRecords.swift in Sources */,
7A761C042677F18E0005F28F /* ApiService.swift in Sources */,
7A1AE6532DFB2AD900ECFC4D /* SwiftDataStorageService+Events.swift in Sources */,
7A95197B2D80B41600E69883 /* AudioRecordServiceProtocol.swift in Sources */,
7AF6D21C2677C1680086EA64 /* DebugInfo.swift in Sources */,
7A0818762DF83DB4000219FE /* SDVehicleName.swift in Sources */,
7ABDA80D2D8721B10083C715 /* Substrings.swift in Sources */,
7A2A8F772DEF205A00FC0AE2 /* VehicleNumberType.swift in Sources */,
7AF6D2142677C1680086EA64 /* VehicleEvent.swift in Sources */,
7A0818742DF83D98000219FE /* SDVehicleBrand.swift in Sources */,
7A64A2102C19E1EB00284124 /* VehicleBrandDto.swift in Sources */,
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */,
7AF6D2172677C1680086EA64 /* VehicleRegion.swift in Sources */,
@ -1531,12 +1494,10 @@
7AF6D21E2677C1680086EA64 /* PlateNumber.swift in Sources */,
7A5D84C62C1AE72E00C2209B /* VehicleName.swift in Sources */,
7AD8572A2DF87928009E4B72 /* SDAudioRecord.swift in Sources */,
7AD8571E2DF8700F009E4B72 /* SDVehicleOwnershipPeriod.swift in Sources */,
7A64A2122C19E2A100284124 /* VehicleModelDto.swift in Sources */,
7ABDA80B2D8715DC0083C715 /* VehicleRecordError.swift in Sources */,
7A06E0B32C707E13005731AC /* SettingsServiceProtocol.swift in Sources */,
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */,
7AD857282DF87733009E4B72 /* SDDebugInfo.swift in Sources */,
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */,
7AA515D02D9ABCC800EB3418 /* RecordPlayerService.swift in Sources */,
7A60D24D2C5A9D4900D13F7B /* LocationService.swift in Sources */,
@ -1544,10 +1505,8 @@
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
7A64A2032C19DA1000284124 /* VehicleDto.swift in Sources */,
7AB4E4332D3C21C00006D052 /* FileManagerExt.swift in Sources */,
7AD857262DF876C9009E4B72 /* SDVehicleNote.swift in Sources */,
7A9519792D80B3E800E69883 /* AudioRecordService.swift in Sources */,
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */,
7A08187E2DF84CE0000219FE /* SDVehiclePhoto.swift in Sources */,
7AB4E43B2D3D3F4F0006D052 /* VehicleServiceProtocol.swift in Sources */,
7AA514E02D0B75B3001CAC50 /* StorageService+Events.swift in Sources */,
7A64A2222C19E99E00284124 /* DebugInfoDto.swift in Sources */,
@ -1559,13 +1518,10 @@
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */,
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
7AA515D22D9ABCE600EB3418 /* RecordPlayerServiceProtocol.swift in Sources */,
7A0818782DF84AE4000219FE /* SDVehicleModel.swift in Sources */,
7A08187A2DF84B77000219FE /* SDVehicleEngine.swift in Sources */,
7AA515DA2D9ADEF000EB3418 /* StorageError.swift in Sources */,
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */,
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
7AD857222DF875B2009E4B72 /* SDOsago.swift in Sources */,
7ABDA80F2D8723F90083C715 /* StorageService+AudioRecords.swift in Sources */,
7A64A2142C19E3B700284124 /* VehicleEngineDto.swift in Sources */,
7A1AE64F2DFB22EC00ECFC4D /* SwiftDataStorageService+Utils.swift in Sources */,

View File

@ -8,13 +8,13 @@
import Foundation
public enum DebugInfoStatus: Int, Sendable, Decodable, Equatable, Hashable {
public enum DebugInfoStatus: Int, Sendable, Codable, Equatable, Hashable {
case success = 0
case error = 1
case warning = 2
}
public struct DebugInfoDto: Decodable, Sendable, Equatable, Hashable {
public struct DebugInfoDto: Codable, Sendable, Equatable, Hashable {
public var autocod: DebugInfoEntryDto?
public var vin01vin: DebugInfoEntryDto?
@ -23,7 +23,7 @@ public struct DebugInfoDto: Decodable, Sendable, Equatable, Hashable {
public var nomerogram: DebugInfoEntryDto?
}
public struct DebugInfoEntryDto: Decodable, Sendable, Equatable, Hashable {
public struct DebugInfoEntryDto: Codable, Sendable, Equatable, Hashable {
public var fields: Int64 = 0
public var error: String?

View File

@ -8,7 +8,7 @@
import Foundation
public struct OsagoDto: Decodable, Sendable, Equatable, Hashable {
public struct OsagoDto: Codable, Sendable, Equatable, Hashable {
public var date: TimeInterval = 0
public var number: String = ""

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehicleAdDto: Decodable, Sendable, Hashable, Equatable {
public struct VehicleAdDto: Codable, Sendable, Hashable, Equatable {
public var id: Int = 0
public var url: String?

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehicleBrandDto: Decodable, Sendable, Equatable, Hashable {
public struct VehicleBrandDto: Codable, Sendable, Equatable, Hashable {
public var name: VehicleNameDto?
public var logo: String?

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehicleEngineDto: Decodable, Sendable, Equatable, Hashable {
public struct VehicleEngineDto: Codable, Sendable, Equatable, Hashable {
public var number: String?
public var volume: Int? = 0

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehicleModelDto: Decodable, Sendable, Equatable, Hashable {
public struct VehicleModelDto: Codable, Sendable, Equatable, Hashable {
public var name: VehicleNameDto?
}

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehicleNameDto: Decodable, Sendable, Equatable, Hashable {
public struct VehicleNameDto: Codable, Sendable, Equatable, Hashable {
public var original: String?
public var normalized: String?

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehicleOwnershipPeriodDto: Decodable, Sendable, Equatable, Hashable {
public struct VehicleOwnershipPeriodDto: Codable, Sendable, Equatable, Hashable {
public var lastOperation: String = ""
public var ownerType: String = ""

View File

@ -8,7 +8,7 @@
import Foundation
public struct VehiclePhotoDto: Decodable, Sendable, Equatable, Identifiable, Hashable {
public struct VehiclePhotoDto: Codable, Sendable, Equatable, Identifiable, Hashable {
public let id = UUID()
public var brand: String?

View File

@ -20,8 +20,7 @@ final class SDAudioRecord {
var addedDate: TimeInterval
var duration: TimeInterval
@Relationship(deleteRule: .cascade)
var event: SDVehicleEvent?
var event: VehicleEventDto?
init(
path: String,
@ -29,7 +28,7 @@ final class SDAudioRecord {
rawText: String,
addedDate: TimeInterval,
duration: TimeInterval,
event: SDVehicleEvent? = nil
event: VehicleEventDto? = nil
) {
self.path = path
self.number = number
@ -50,7 +49,7 @@ extension SDAudioRecord: DtoConvertible {
raw: rawText,
addedDate: addedDate,
duration: duration,
event: event?.dto
event: event
)
}
@ -62,7 +61,7 @@ extension SDAudioRecord: DtoConvertible {
rawText: dto.rawText,
addedDate: dto.addedDate,
duration: dto.duration,
event: SDVehicleEvent(dto: dto.event)
event: dto.event
)
}
}

View File

@ -1,93 +0,0 @@
//
// SDDebugInfo.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftData
@Model
final class SDDebugInfo {
@Relationship(deleteRule: .cascade) var autocod: SDDebugInfoEntry?
@Relationship(deleteRule: .cascade) var vin01vin: SDDebugInfoEntry?
@Relationship(deleteRule: .cascade) var vin01base: SDDebugInfoEntry?
@Relationship(deleteRule: .cascade) var vin01history: SDDebugInfoEntry?
@Relationship(deleteRule: .cascade) var nomerogram: SDDebugInfoEntry?
init(
autocod: SDDebugInfoEntry? = nil,
vin01vin: SDDebugInfoEntry? = nil,
vin01base: SDDebugInfoEntry? = nil,
vin01history: SDDebugInfoEntry? = nil,
nomerogram: SDDebugInfoEntry? = nil
) {
self.autocod = autocod
self.vin01vin = vin01vin
self.vin01base = vin01base
self.vin01history = vin01history
self.nomerogram = nomerogram
}
}
@Model
final class SDDebugInfoEntry {
var fields: Int64
var error: String?
var status: Int
init(fields: Int64, error: String? = nil, status: Int) {
self.fields = fields
self.error = error
self.status = status
}
}
extension SDDebugInfo: DtoConvertible {
public var dto: DebugInfoDto {
DebugInfoDto(
autocod: autocod?.dto,
vin01vin: vin01vin?.dto,
vin01base: vin01base?.dto,
vin01history: vin01history?.dto,
nomerogram: nomerogram?.dto
)
}
public convenience init(dto: DebugInfoDto) {
self.init(
autocod: SDDebugInfoEntry(dto: dto.autocod),
vin01vin: SDDebugInfoEntry(dto: dto.vin01vin),
vin01base: SDDebugInfoEntry(dto: dto.vin01base),
vin01history: SDDebugInfoEntry(dto: dto.vin01history),
nomerogram: SDDebugInfoEntry(dto: dto.nomerogram)
)
}
}
extension SDDebugInfoEntry: DtoConvertible {
public var dto: DebugInfoEntryDto {
DebugInfoEntryDto(
fields: fields,
error: error,
status: DebugInfoStatus(rawValue: status) ?? .success
)
}
public convenience init(dto: DebugInfoEntryDto) {
self.init(
fields: dto.fields,
error: dto.error,
status: dto.status.rawValue
)
}
}

View File

@ -1,89 +0,0 @@
//
// SDOsago.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
import SwiftData
@Model
final class SDOsago {
var date: TimeInterval
var number: String
var vin: String?
var plateNumber: String?
var name: String
var status: String?
var restrictions: String
var insurant: String?
var owner: String?
var usageRegion: String?
var birthday: String?
init(
date: TimeInterval,
number: String,
vin: String? = nil,
plateNumber: String? = nil,
name: String,
status: String? = nil,
restrictions: String,
insurant: String? = nil,
owner: String? = nil,
usageRegion: String? = nil,
birthday: String? = nil
) {
self.date = date
self.number = number
self.vin = vin
self.plateNumber = plateNumber
self.name = name
self.status = status
self.restrictions = restrictions
self.insurant = insurant
self.owner = owner
self.usageRegion = usageRegion
self.birthday = birthday
}
}
extension SDOsago: DtoConvertible {
public var dto: OsagoDto {
OsagoDto(
date: date,
number: number,
vin: vin,
plateNumber: plateNumber,
name: name,
status: status,
restrictions: restrictions,
insurant: insurant,
owner: owner,
usageRegion: usageRegion,
birthday: birthday
)
}
public convenience init(dto: OsagoDto) {
self.init(
date: dto.date,
number: dto.number,
vin: dto.vin,
plateNumber: dto.plateNumber,
name: dto.name,
status: dto.status,
restrictions: dto.restrictions,
insurant: dto.insurant,
owner: dto.owner,
usageRegion: dto.usageRegion,
birthday: dto.birthday
)
}
}

View File

@ -12,22 +12,15 @@ import SwiftData
@Model
final class SDVehicle {
@Relationship(deleteRule: .cascade)
var brand: SDVehicleBrand?
@Relationship(deleteRule: .cascade)
var model: SDVehicleModel?
var color: String?
var year: Int
var category: String?
@Relationship(deleteRule: .cascade)
var engine: SDVehicleEngine?
@Attribute(.unique)
var number: String
var brand: VehicleBrandDto?
var model: VehicleModelDto?
var color: String?
var year: Int
var category: String?
var engine: VehicleEngineDto?
var currentNumber: String?
var vin1: String?
var vin2: String?
@ -38,37 +31,22 @@ final class SDVehicle {
var addedDate: TimeInterval
var updatedDate: TimeInterval
var addedBy: String
@Relationship(deleteRule: .cascade)
var photos: [SDVehiclePhoto]
@Relationship(deleteRule: .cascade)
var ownershipPeriods: [SDVehicleOwnershipPeriod]
@Relationship(deleteRule: .cascade)
var events: [SDVehicleEvent]
@Relationship(deleteRule: .cascade)
var osagoContracts: [SDOsago]
@Relationship(deleteRule: .cascade)
var ads: [SDVehicleAd]
@Relationship(deleteRule: .cascade)
var notes: [SDVehicleNote]
@Relationship(deleteRule: .cascade)
var debugInfo: SDDebugInfo?
var photos: [VehiclePhotoDto]
var ownershipPeriods: [VehicleOwnershipPeriodDto]
var events: [VehicleEventDto]
var osagoContracts: [OsagoDto]
var ads: [VehicleAdDto]
var notes: [VehicleNoteDto]
var debugInfo: DebugInfoDto?
var synchronized: Bool
init(
brand: SDVehicleBrand? = nil,
model: SDVehicleModel? = nil,
brand: VehicleBrandDto? = nil,
model: VehicleModelDto? = nil,
color: String? = nil,
year: Int,
category: String? = nil,
engine: SDVehicleEngine? = nil,
engine: VehicleEngineDto? = nil,
number: String,
currentNumber: String? = nil,
vin1: String? = nil,
@ -80,13 +58,13 @@ final class SDVehicle {
addedDate: TimeInterval,
updatedDate: TimeInterval,
addedBy: String,
photos: [SDVehiclePhoto],
ownershipPeriods: [SDVehicleOwnershipPeriod],
events: [SDVehicleEvent],
osagoContracts: [SDOsago],
ads: [SDVehicleAd],
notes: [SDVehicleNote],
debugInfo: SDDebugInfo? = nil,
photos: [VehiclePhotoDto],
ownershipPeriods: [VehicleOwnershipPeriodDto],
events: [VehicleEventDto],
osagoContracts: [OsagoDto],
ads: [VehicleAdDto],
notes: [VehicleNoteDto],
debugInfo: DebugInfoDto? = nil,
synchronized: Bool
) {
self.brand = brand
@ -126,11 +104,11 @@ extension SDVehicle: DtoConvertible {
vehicle.currentNumber = self.currentNumber
vehicle.addedDate = self.addedDate
vehicle.updatedDate = self.updatedDate
vehicle.brand = self.brand?.dto
vehicle.brand = self.brand
vehicle.synchronized = self.synchronized
vehicle.notes = notes.map(\.dto)
vehicle.events = events.map(\.dto).sorted { $0.date > $1.date }
vehicle.notes = notes
vehicle.events = events.sorted { $0.date > $1.date }
return vehicle
}
@ -138,12 +116,12 @@ extension SDVehicle: DtoConvertible {
var dto: VehicleDto {
var vehicle = VehicleDto()
vehicle.brand = brand?.dto
vehicle.model = model?.dto
vehicle.brand = brand
vehicle.model = model
vehicle.color = color
vehicle.year = year
vehicle.category = category
vehicle.engine = engine?.dto
vehicle.engine = engine
vehicle.number = number
vehicle.currentNumber = currentNumber
vehicle.vin1 = vin1
@ -155,13 +133,13 @@ extension SDVehicle: DtoConvertible {
vehicle.addedDate = addedDate
vehicle.updatedDate = updatedDate
vehicle.addedBy = addedBy
vehicle.photos = photos.map(\.dto)
vehicle.ownershipPeriods = ownershipPeriods.map(\.dto)
vehicle.events = events.map(\.dto)
vehicle.osagoContracts = osagoContracts.map(\.dto)
vehicle.ads = ads.map(\.dto)
vehicle.notes = notes.map(\.dto)
vehicle.debugInfo = debugInfo?.dto
vehicle.photos = photos
vehicle.ownershipPeriods = ownershipPeriods
vehicle.events = events
vehicle.osagoContracts = osagoContracts
vehicle.ads = ads
vehicle.notes = notes
vehicle.debugInfo = debugInfo
vehicle.synchronized = synchronized
return vehicle
}
@ -169,12 +147,12 @@ extension SDVehicle: DtoConvertible {
convenience init(dto: VehicleDto) {
self.init(
brand: SDVehicleBrand(dto: dto.brand),
model: SDVehicleModel(dto: dto.model),
brand: dto.brand,
model: dto.model,
color: dto.color,
year: dto.year,
category: dto.category,
engine: SDVehicleEngine(dto: dto.engine),
engine: dto.engine,
number: dto.number,
currentNumber: dto.currentNumber,
vin1: dto.vin1,
@ -186,13 +164,13 @@ extension SDVehicle: DtoConvertible {
addedDate: dto.addedDate,
updatedDate: dto.updatedDate,
addedBy: dto.addedBy,
photos: dto.photos.map(SDVehiclePhoto.init),
ownershipPeriods: dto.ownershipPeriods.map(SDVehicleOwnershipPeriod.init),
events: dto.events.map(SDVehicleEvent.init),
osagoContracts: dto.osagoContracts.map(SDOsago.init),
ads: dto.ads.map(SDVehicleAd.init),
notes: dto.notes.map(SDVehicleNote.init),
debugInfo: SDDebugInfo(dto: dto.debugInfo),
photos: dto.photos,
ownershipPeriods: dto.ownershipPeriods,
events: dto.events,
osagoContracts: dto.osagoContracts,
ads: dto.ads,
notes: dto.notes,
debugInfo: dto.debugInfo,
synchronized: dto.synchronized
)
}

View File

@ -1,79 +0,0 @@
//
// SDVehicleAd.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
import SwiftData
@Model
final class SDVehicleAd {
var id: Int
var url: String?
var price: String?
var date: TimeInterval
var mileage: String?
var region: String?
var city: String?
var adDescription: String?
var photos: [String]
init(
id: Int,
url: String? = nil,
price: String? = nil,
date: TimeInterval,
mileage: String? = nil,
region: String? = nil,
city: String? = nil,
adDescription: String? = nil,
photos: [String]
) {
self.id = id
self.url = url
self.price = price
self.date = date
self.mileage = mileage
self.region = region
self.city = city
self.adDescription = adDescription
self.photos = photos
}
}
extension SDVehicleAd: DtoConvertible {
public var dto: VehicleAdDto {
VehicleAdDto(
id: id,
url: url,
price: price,
date: date,
mileage: mileage,
region: region,
city: city,
adDescription: adDescription,
photos: photos
)
}
public convenience init(dto: VehicleAdDto) {
self.init(
id: dto.id,
url: dto.url,
price: dto.price,
date: dto.date,
mileage: dto.mileage,
region: dto.region,
city: dto.city,
adDescription: dto.adDescription,
photos: dto.photos
)
}
}

View File

@ -1,38 +0,0 @@
//
// SDVehicleBrand.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftData
@Model
final class SDVehicleBrand {
@Relationship(deleteRule: .cascade)
var name: SDVehicleName?
var logo: String?
init(name: SDVehicleName? = nil, logo: String? = nil) {
self.name = name
self.logo = logo
}
}
extension SDVehicleBrand: DtoConvertible {
var dto: VehicleBrandDto {
VehicleBrandDto(name: name?.dto, logo: logo)
}
convenience init(dto: VehicleBrandDto) {
self.init(
name: SDVehicleName(dto: dto.name),
logo: dto.logo
)
}
}

View File

@ -1,58 +0,0 @@
//
// SDVehicleEngine.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftData
@Model
final class SDVehicleEngine {
var number: String?
var volume: Int?
var powerHp: Float?
var powerKw: Float?
var fuelType: String?
init(
number: String? = nil,
volume: Int? = nil,
powerHp: Float? = nil,
powerKw: Float? = nil,
fuelType: String? = nil
) {
self.number = number
self.volume = volume
self.powerHp = powerHp
self.powerKw = powerKw
self.fuelType = fuelType
}
}
extension SDVehicleEngine: DtoConvertible {
var dto: VehicleEngineDto {
VehicleEngineDto(
number: number,
volume: volume,
powerHp: powerHp,
powerKw: powerKw,
fuelType: fuelType
)
}
convenience init(dto: VehicleEngineDto) {
self.init(
number: dto.number,
volume: dto.volume,
powerHp: dto.powerHp,
powerKw: dto.powerKw,
fuelType: dto.fuelType
)
}
}

View File

@ -1,68 +0,0 @@
//
// SDVehicleEvent.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
import SwiftData
@Model
final class SDVehicleEvent {
@Attribute(.unique)
var id: String
var date: TimeInterval
var latitude: Double
var longitude: Double
var address: String?
var addedBy: String?
init(
id: String,
date: TimeInterval,
latitude: Double,
longitude: Double,
address: String? = nil,
addedBy: String? = nil
) {
self.id = id
self.date = date
self.latitude = latitude
self.longitude = longitude
self.address = address
self.addedBy = addedBy
}
}
extension SDVehicleEvent: DtoConvertible {
public var dto: VehicleEventDto {
var dto = VehicleEventDto(
lat: latitude,
lon: longitude,
addedBy: addedBy
)
dto.id = id
dto.date = date
dto.address = address
return dto
}
public convenience init(dto: VehicleEventDto) {
self.init(
id: dto.id,
date: dto.date,
latitude: dto.latitude,
longitude: dto.longitude,
address: dto.address,
addedBy: dto.addedBy
)
}
}

View File

@ -1,33 +0,0 @@
//
// SDVehicleModel.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftData
@Model
final class SDVehicleModel {
@Relationship(deleteRule: .cascade)
var name: SDVehicleName?
init(name: SDVehicleName? = nil) {
self.name = name
}
}
extension SDVehicleModel: DtoConvertible {
var dto: VehicleModelDto {
VehicleModelDto(name: name?.dto)
}
convenience init(dto: VehicleModelDto) {
self.init(name: SDVehicleName(dto: dto.name))
}
}

View File

@ -1,40 +0,0 @@
//
// SDVehicleName.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftData
@Model
final class SDVehicleName {
var original: String?
var normalized: String?
init(original: String? = nil, normalized: String? = nil) {
self.original = original
self.normalized = normalized
}
}
extension SDVehicleName: DtoConvertible {
var dto: VehicleNameDto {
VehicleNameDto(
original: original,
normalized: normalized
)
}
convenience init(dto: VehicleNameDto) {
self.init(
original: dto.original,
normalized: dto.normalized
)
}
}

View File

@ -1,63 +0,0 @@
//
// SDVehicleNote.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
import SwiftData
@Model
final class SDVehicleNote {
@Attribute(.unique)
var id: String
var user: String
var date: TimeInterval
var text: String
init(
id: String,
user: String,
date: TimeInterval,
text: String
) {
self.id = id
self.user = user
self.date = date
self.text = text
}
convenience init(text: String, user: String) {
self.init(
id: UUID().uuidString,
user: user,
date: Date().timeIntervalSince1970,
text: text
)
}
}
extension SDVehicleNote: DtoConvertible {
public var dto: VehicleNoteDto {
var dto = VehicleNoteDto(text: text, user: user)
dto.id = id
dto.date = date
return dto
}
public convenience init(dto: VehicleNoteDto) {
self.init(
id: dto.id,
user: dto.user,
date: dto.date,
text: dto.text
)
}
}

View File

@ -1,88 +0,0 @@
//
// SDVehicleOwnershipPeriod.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import SwiftData
@Model
final class SDVehicleOwnershipPeriod {
var lastOperation: String
var ownerType: String
var from: Int64
var to: Int64
var region: String?
var registrationRegion: String?
var locality: String?
var code: String?
var street: String?
var building: String?
var inn: String?
init(
lastOperation: String,
ownerType: String,
from: Int64,
to: Int64,
region: String? = nil,
registrationRegion: String? = nil,
locality: String? = nil,
code: String? = nil,
street: String? = nil,
building: String? = nil,
inn: String? = nil
) {
self.lastOperation = lastOperation
self.ownerType = ownerType
self.from = from
self.to = to
self.region = region
self.registrationRegion = registrationRegion
self.locality = locality
self.code = code
self.street = street
self.building = building
self.inn = inn
}
}
extension SDVehicleOwnershipPeriod: DtoConvertible {
public var dto: VehicleOwnershipPeriodDto {
VehicleOwnershipPeriodDto(
lastOperation: lastOperation,
ownerType: ownerType,
from: from,
to: to,
region: region,
registrationRegion: registrationRegion,
locality: locality,
code: code,
street: street,
building: building,
inn: inn
)
}
convenience public init(dto: VehicleOwnershipPeriodDto) {
self.init(
lastOperation: dto.lastOperation,
ownerType: dto.ownerType,
from: dto.from,
to: dto.to,
region: dto.region,
registrationRegion: dto.registrationRegion,
locality: dto.locality,
code: dto.code,
street: dto.street,
building: dto.building,
inn: dto.inn
)
}
}

View File

@ -1,54 +0,0 @@
//
// SDVehiclePhoto.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 10.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Foundation
import SwiftData
@Model
final class SDVehiclePhoto {
var brand: String?
var model: String?
var date: TimeInterval
var url: String
init(
brand: String? = nil,
model: String? = nil,
date: TimeInterval,
url: String
) {
self.brand = brand
self.model = model
self.date = date
self.url = url
}
}
extension SDVehiclePhoto: DtoConvertible {
public var dto: VehiclePhotoDto {
VehiclePhotoDto(
brand: brand,
model: model,
date: date,
url: url
)
}
public convenience init(dto: VehiclePhotoDto) {
self.init(
brand: dto.brand,
model: dto.model,
date: dto.date,
url: dto.url
)
}
}

View File

@ -16,7 +16,7 @@ extension SwiftDataStorageService {
throw StorageError.vehicleNotFound
}
vehicle.events.append(SDVehicleEvent(dto: event))
vehicle.events.append(event)
vehicle.updatedDate = Date().timeIntervalSince1970
try context.save()
@ -45,7 +45,7 @@ extension SwiftDataStorageService {
}
if let index = vehicle.events.firstIndex(where: { $0.id == event.id }) {
vehicle.events[index] = SDVehicleEvent(dto: event)
vehicle.events[index] = event
vehicle.updatedDate = Date().timeIntervalSince1970
try context.save()
} else {

View File

@ -16,7 +16,7 @@ extension SwiftDataStorageService {
throw StorageError.vehicleNotFound
}
let note = SDVehicleNote(text: text, user: settingsService.user.email)
let note = VehicleNoteDto(text: text, user: settingsService.user.email)
vehicle.notes.append(note)
vehicle.updatedDate = Date().timeIntervalSince1970
@ -30,12 +30,12 @@ extension SwiftDataStorageService {
throw StorageError.vehicleNotFound
}
guard let note = try? fetchNote(id: id) else {
guard let index = vehicle.notes.firstIndex(where: { $0.id == id }) else {
throw StorageError.noteNotFound
}
vehicle.notes.remove(at: index)
vehicle.updatedDate = Date().timeIntervalSince1970
context.delete(note)
try context.save()
return vehicle.dto
@ -46,11 +46,11 @@ extension SwiftDataStorageService {
throw StorageError.vehicleNotFound
}
guard let note = try? fetchNote(id: id) else {
guard let index = vehicle.notes.firstIndex(where: { $0.id == id }) else {
throw StorageError.noteNotFound
}
note.text = text
vehicle.notes[index].text = text
vehicle.updatedDate = Date().timeIntervalSince1970
try context.save()

View File

@ -29,15 +29,6 @@ extension SwiftDataStorageService {
return vehicles.first
}
func fetchNote(id: String) throws -> SDVehicleNote? {
let notes = try context.fetch(
FetchDescriptor<SDVehicleNote>(predicate: #Predicate { $0.id == id })
)
return notes.first
}
func fetchAudioRecord(id: String) throws -> SDAudioRecord? {
let records = try context.fetch(

View File

@ -21,7 +21,7 @@ public final class SwiftDataStorageService: StorageServiceProtocol {
self.settingsService = settingsService
self.container = try ModelContainer(
for: SDVehicle.self, SDVehicleNote.self, SDAudioRecord.self,
for: SDVehicle.self, SDAudioRecord.self,
configurations: .init(isStoredInMemoryOnly: isTest)
)

View File

@ -0,0 +1,65 @@
//
// SwiftDataStorageServiceTests+AudioRecords.swift
// AutoCatCoreTests
//
// Created by Selim Mustafaev on 13.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Testing
import AutoCatCore
extension SwiftDataStorageServiceTests {
@Test("Audio records save/load")
func audioRecordsSaveLoad() async throws {
try await storageService.add(record: .default)
let records = try await storageService.loadRecords()
#expect(records.count == 1)
#expect(records.first?.path == AudioRecordDto.testPath)
}
@Test("Audio records save/delete")
func audioRecordsSaveDelete() async throws {
try await storageService.add(record: .default)
try await storageService.deleteRecord(id: AudioRecordDto.default.id)
let records = try await storageService.loadRecords()
#expect(records.isEmpty)
}
@Test("Audio records delete wrong record")
func audioRecordsDeleteWrongRecord() async throws {
await #expect(throws: StorageError.recordNotFound) {
try await storageService.deleteRecord(id: AudioRecordDto.default.id)
}
}
@Test("Audio record update")
func audioRecordUpdate() async throws {
try await storageService.add(record: .default)
let updatedRecord = try await storageService.updateRecord(
id: AudioRecordDto.default.id,
number: "123"
)
#expect(updatedRecord.path == AudioRecordDto.testPath)
#expect(updatedRecord.number == "123")
}
@Test("Audio records update wrong record")
func audioRecordUpdateWrongRecord() async throws {
await #expect(throws: StorageError.recordNotFound) {
_ = try await storageService.updateRecord(
id: AudioRecordDto.default.id,
number: "123"
)
}
}
}

View File

@ -0,0 +1,101 @@
//
// SwiftDataStorageServiceTests+Events.swift
// AutoCatCoreTests
//
// Created by Selim Mustafaev on 13.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Testing
import AutoCatCore
extension SwiftDataStorageServiceTests {
@Test("Add event")
func addEvent() async throws {
let event = VehicleEventDto(lat: testLat, lon: testLon, addedBy: nil)
let vehicle = try await storageService.add(event: event, to: VehicleDto.validNumber)
#expect(vehicle.events.count == 1)
#expect(vehicle.events.first?.latitude == testLat)
#expect(vehicle.events.first?.longitude == testLon)
#expect(vehicle.updatedDate != 0)
}
@Test("Add event to non-existent vehicle")
func addEventToNonExistentVehicle() async throws {
let event = VehicleEventDto(lat: testLat, lon: testLon, addedBy: nil)
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.add(event: event, to: "")
}
}
@Test("Remove event")
func removeEvent() async throws {
let event = VehicleEventDto(lat: testLat, lon: testLon, addedBy: nil)
var vehicle = try await storageService.add(event: event, to: VehicleDto.validNumber)
let id = try #require(vehicle.events.first { $0.latitude == testLat && $0.longitude == testLon }?.id)
vehicle = try await storageService.remove(event: id, from: VehicleDto.validNumber)
#expect(vehicle.events.isEmpty)
#expect(vehicle.updatedDate != 0)
}
@Test("Remove event from non-existent vehicle")
func removeEventFromNonExistentVehicle() async throws {
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.remove(event: "", from: "")
}
}
@Test("Remove non-existent event")
func removeNonExistentEvent() async throws {
await #expect(throws: StorageError.eventNotFound) {
_ = try await storageService.remove(event: "", from: VehicleDto.validNumber)
}
}
@Test("Edit event")
func editEvent() async throws {
let event = VehicleEventDto(lat: 0, lon: 0, addedBy: nil)
var vehicle = try await storageService.add(event: event, to: VehicleDto.validNumber)
var editedEvent = VehicleEventDto(lat: testLat, lon: testLon, addedBy: nil)
editedEvent.id = try #require(vehicle.events.first?.id)
_ = try await storageService.edit(event: editedEvent, for: VehicleDto.validNumber)
vehicle = try await storageService.loadVehicle(number: VehicleDto.validNumber)
let resultEvent = try #require(vehicle.events.first { $0.id == editedEvent.id })
#expect(resultEvent.latitude == testLat && resultEvent.longitude == testLon)
#expect(vehicle.events.count == 1)
#expect(vehicle.updatedDate != 0)
}
@Test("Edit non-existent event")
func editNonExistentEvent() async throws {
let event = VehicleEventDto(lat: 0, lon: 0, addedBy: nil)
await #expect(throws: StorageError.eventNotFound) {
_ = try await storageService.edit(event: event, for: VehicleDto.validNumber)
}
}
@Test("Edit event in non-existent vehicle")
func editEventInNonExistentVehicle() async throws {
let event = VehicleEventDto(lat: 0, lon: 0, addedBy: nil)
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.edit(event: event, for: "")
}
}
}

View File

@ -0,0 +1,90 @@
//
// SwiftDataStorageServiceTests+Notes.swift
// AutoCatCoreTests
//
// Created by Selim Mustafaev on 13.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Testing
import AutoCatCore
extension SwiftDataStorageServiceTests {
@Test("Add note to vehicle")
func addNote() async throws {
let vehicle = try await storageService.addNote(text: noteText, to: VehicleDto.validNumber)
#expect(vehicle.number == VehicleDto.validNumber)
#expect(vehicle.notes.contains { $0.text == noteText })
}
@Test("Adding note to non-existent vehicle")
func addNoteError() async throws {
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.addNote(text: noteText, to: "")
}
}
@Test("Edit note")
func editNote() async throws {
let newNoteText = "New test text"
var vehicle = try await storageService.addNote(text: noteText, to: VehicleDto.validNumber)
let note = try #require(vehicle.notes.first { $0.text == noteText })
_ = try await storageService.editNote(id: note.id, text: newNoteText, for: VehicleDto.validNumber)
vehicle = try await storageService.loadVehicle(number: VehicleDto.validNumber)
#expect(vehicle.number == VehicleDto.validNumber)
#expect(vehicle.notes.contains { $0.text == newNoteText })
#expect(!vehicle.notes.contains { $0.text == noteText })
}
@Test("Edit note of non-existent vehicle")
func addNoteNonExistentVehicle() async throws {
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.editNote(id: "", text: "", for: "")
}
}
@Test("Edit non-existent note")
func editNonExistentNote() async throws {
await #expect(throws: StorageError.noteNotFound) {
_ = try await storageService.editNote(id: "", text: "", for: VehicleDto.validNumber)
}
}
@Test("Delete note")
func deleteNote() async throws {
var vehicle = try await storageService.addNote(text: noteText, to: VehicleDto.validNumber)
let note = try #require(vehicle.notes.first { $0.text == noteText })
vehicle = try await storageService.deleteNote(id: note.id, for: VehicleDto.validNumber)
#expect(vehicle.number == VehicleDto.validNumber)
#expect(!vehicle.notes.contains { $0.text == noteText })
}
@Test("Delete note from non-existent vehicle")
func deleteNoteNonExistentVehicle() async throws {
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.deleteNote(id: "", for: "")
}
}
@Test("Delete non-existent note")
func deleteNonExistentNote() async throws {
await #expect(throws: StorageError.noteNotFound) {
_ = try await storageService.deleteNote(id: "", for: VehicleDto.validNumber)
}
}
}

View File

@ -0,0 +1,100 @@
//
// SwiftDataStorageServiceTests.swift
// AutoCatCoreTests
//
// Created by Selim Mustafaev on 13.06.2025.
// Copyright © 2025 Selim Mustafaev. All rights reserved.
//
import Testing
import Mockable
import SwiftData
import Foundation
@testable import AutoCatCore
@MainActor
struct SwiftDataStorageServiceTests {
let settingsServiceMock = MockSettingsServiceProtocol()
let storageService: SwiftDataStorageService
let noteText = "Test note text"
let testLat: Double = 42
let testLon: Double = 24
init() async throws {
storageService = try .init(settingsService: settingsServiceMock, isTest: true)
var vehicle: VehicleDto = .normal
vehicle.addedDate = 0
vehicle.updatedDate = 0
try await storageService.updateVehicle(dto: vehicle, policy: .always)
given(settingsServiceMock)
.user.willReturn(User())
}
@Test("Load existing vehicle")
func loadExistingVehicle() async throws {
let vehicle = try await storageService.loadVehicle(number: VehicleDto.validNumber)
#expect(vehicle.number == VehicleDto.validNumber)
}
@Test("Load non-existent vehicle")
func loadNonExistentVehicle() async throws {
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.loadVehicle(number: "")
}
}
@Test("Update existing vehicle", arguments: DbUpdatePolicy.allCases)
func updateVehicle(policy: DbUpdatePolicy) async throws {
var vehicle: VehicleDto = .normal
vehicle.updatedDate = 123
let updated = try await storageService.updateVehicle(dto: vehicle, policy: policy)
let vehicles = await storageService.loadVehicles()
#expect(updated == true)
#expect(vehicles.count == 1)
#expect(vehicles.first?.updatedDate == 123)
}
@Test("Update non-existent vehicle", arguments: DbUpdatePolicy.allCases)
func updateNonExistentVehicle(policy: DbUpdatePolicy) async throws {
let updated = try await storageService.updateVehicle(dto: .normal2, policy: policy)
let vehicles = await storageService.loadVehicles()
switch policy {
case .always:
#expect(updated == true)
#expect(vehicles.count == 2)
case .ifExists:
#expect(updated == false)
#expect(vehicles.count == 1)
}
}
@Test("Delete vehicle")
func deleteVehicle() async throws {
try await storageService.deleteVehicle(number: VehicleDto.validNumber)
await #expect(throws: StorageError.vehicleNotFound) {
_ = try await storageService.loadVehicle(number: VehicleDto.validNumber)
}
}
@Test("Delete non-existent vehicle")
func deleteNonExistentVehicle() async throws {
await #expect(throws: StorageError.vehicleNotFound) {
try await storageService.deleteVehicle(number: "")
}
}
}