InMemoryCrmDatabase.swift 513 lignes · 15304 octets
import Foundation
@testable import Card2vcf

/// In-memory `CrmDatabase` double (Android tests used a Room in-memory database).
/// Reproduces the Room semantics documented on each DAO port.
final class InMemoryCrmDatabase: CrmDatabase {
    let contactDaoImpl = InMemoryCrmContactDao()
    let entrepriseDaoImpl = InMemoryEntrepriseDao()
    let projetDaoImpl = InMemoryProjetDao()
    let tacheDaoImpl = InMemoryTacheDao()
    let interactionDaoImpl = InMemoryInteractionDao()
    let workflowDaoImpl = InMemoryWorkflowDao()
    let rdvDaoImpl = InMemoryRdvDao()
    let reservationDaoImpl = InMemoryReservationDao()
    let indisponibiliteDaoImpl = InMemoryIndisponibiliteDao()
    let syncMetaDaoImpl = InMemorySyncMetaDao()
    let syncOpDaoImpl = InMemorySyncOpDao()
    let noteProjetDaoImpl = InMemoryNoteProjetDao()

    var crmContactDao: CrmContactDao { contactDaoImpl }
    var entrepriseDao: EntrepriseDao { entrepriseDaoImpl }
    var projetDao: ProjetDao { projetDaoImpl }
    var tacheDao: TacheDao { tacheDaoImpl }
    var interactionDao: InteractionDao { interactionDaoImpl }
    var workflowDao: WorkflowDao { workflowDaoImpl }
    var rdvDao: RdvDao { rdvDaoImpl }
    var reservationDao: ReservationDao { reservationDaoImpl }
    var indisponibiliteDao: IndisponibiliteDao { indisponibiliteDaoImpl }
    var syncMetaDao: SyncMetaDao { syncMetaDaoImpl }
    var syncOpDao: SyncOpDao { syncOpDaoImpl }
    var noteProjetDao: NoteProjetDao { noteProjetDaoImpl }
}

final class InMemoryCrmContactDao: CrmContactDao {
    private var rows: [CrmContactEntity] = []
    private var exclusions: [DuplicateExclusionEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func insert(_ entity: CrmContactEntity) async throws -> Int64 {
        var row = entity
        if row.id == 0 {
            row.id = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.id == row.id }
            nextId = max(nextId, row.id + 1)
        }
        rows.append(row)
        return row.id
    }

    func update(_ entity: CrmContactEntity) async throws {
        if let index = rows.firstIndex(where: { $0.id == entity.id }) {
            rows[index] = entity
        }
    }

    func deleteById(_ id: Int64) async throws {
        rows.removeAll { $0.id == id }
    }

    func getById(_ id: Int64) async throws -> CrmContactEntity? {
        rows.first { $0.id == id }
    }

    func getByServerId(_ serverId: String) async throws -> CrmContactEntity? {
        rows.first { $0.serverId == serverId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func fetchAll() async throws -> [CrmContactEntity] {
        rows.sorted { $0.createdAt > $1.createdAt }
    }

    func getAll() async throws -> [CrmContactEntity] {
        try await fetchAll()
    }

    func searchIds(_ query: String) async throws -> [Int64] {
        []
    }

    @discardableResult
    func insertExclusion(_ ex: DuplicateExclusionEntity) async throws -> Int64 {
        if exclusions.contains(where: { $0.idA == ex.idA && $0.idB == ex.idB }) { return -1 }
        var row = ex
        row.id = nextId
        nextId += 1
        exclusions.append(row)
        return row.id
    }

    func getExclusions() async throws -> [DuplicateExclusionEntity] {
        exclusions
    }

    func deleteExclusionsFor(_ id: Int64) async throws {
        exclusions.removeAll { $0.idA == id || $0.idB == id }
    }
}

final class InMemoryEntrepriseDao: EntrepriseDao {
    private var rows: [EntrepriseEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: EntrepriseEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> EntrepriseEntity? {
        rows.first { $0.serverId == serverId }
    }

    func getByLocalId(_ localId: Int64) async throws -> EntrepriseEntity? {
        rows.first { $0.localId == localId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func listAll() async throws -> [EntrepriseEntity] {
        rows
    }
}

final class InMemoryProjetDao: ProjetDao {
    private var rows: [ProjetEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: ProjetEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> ProjetEntity? {
        rows.first { $0.serverId == serverId }
    }

    func getByLocalId(_ localId: Int64) async throws -> ProjetEntity? {
        rows.first { $0.localId == localId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func listAll() async throws -> [ProjetEntity] {
        rows
    }

    func fetchAll() async throws -> [ProjetEntity] {
        rows.sorted { $0.nom < $1.nom }
    }
}

final class InMemoryTacheDao: TacheDao {
    private var rows: [TacheEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: TacheEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> TacheEntity? {
        rows.first { $0.serverId == serverId }
    }

    func getByLocalId(_ localId: Int64) async throws -> TacheEntity? {
        rows.first { $0.localId == localId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func deleteByLocalId(_ localId: Int64) async throws {
        rows.removeAll { $0.localId == localId }
    }

    func listByProjetServerId(_ projetServerId: String) async throws -> [TacheEntity] {
        rows.filter { $0.projetServerId == projetServerId }
    }

    func fetchByProjetServerId(_ projetServerId: String) async throws -> [TacheEntity] {
        rows.filter { $0.projetServerId == projetServerId }.sorted { $0.ordre < $1.ordre }
    }

    func listAll() async throws -> [TacheEntity] {
        rows
    }
}

final class InMemoryInteractionDao: InteractionDao {
    private var rows: [InteractionEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: InteractionEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> InteractionEntity? {
        rows.first { $0.serverId == serverId }
    }

    func getByLocalId(_ localId: Int64) async throws -> InteractionEntity? {
        rows.first { $0.localId == localId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func deleteByLocalId(_ localId: Int64) async throws {
        rows.removeAll { $0.localId == localId }
    }

    func listByContactServerId(_ contactServerId: String) async throws -> [InteractionEntity] {
        rows.filter { $0.contactServerId == contactServerId }
    }

    func fetchByContactServerId(_ contactServerId: String) async throws -> [InteractionEntity] {
        rows.filter { $0.contactServerId == contactServerId }.sorted { $0.createdAt > $1.createdAt }
    }

    func listAll() async throws -> [InteractionEntity] {
        rows
    }
}

final class InMemoryWorkflowDao: WorkflowDao {
    private var rows: [String: WorkflowEntity] = [:]

    func upsert(_ entity: WorkflowEntity) async throws {
        rows[entity.serverId] = entity
    }

    func getByServerId(_ serverId: String) async throws -> WorkflowEntity? {
        rows[serverId]
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeValue(forKey: serverId)
    }

    func listAll() async throws -> [WorkflowEntity] {
        Array(rows.values)
    }
}

final class InMemoryRdvDao: RdvDao {
    private var rows: [RdvEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: RdvEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> RdvEntity? {
        rows.first { $0.serverId == serverId }
    }

    func getByLocalId(_ localId: Int64) async throws -> RdvEntity? {
        rows.first { $0.localId == localId }
    }

    func getByCalendarEventId(_ calendarEventId: Int64) async throws -> RdvEntity? {
        rows.first { $0.calendarEventId == calendarEventId }
    }

    func listDirty() async throws -> [RdvEntity] {
        rows.filter { $0.dirtyLocal }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func deleteByLocalId(_ localId: Int64) async throws {
        rows.removeAll { $0.localId == localId }
    }

    func listAll() async throws -> [RdvEntity] {
        rows
    }
}

final class InMemoryReservationDao: ReservationDao {
    private var rows: [ReservationEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: ReservationEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> ReservationEntity? {
        rows.first { $0.serverId == serverId }
    }

    func getByLocalId(_ localId: Int64) async throws -> ReservationEntity? {
        rows.first { $0.localId == localId }
    }

    func getByCalendarEventId(_ calendarEventId: Int64) async throws -> ReservationEntity? {
        rows.first { $0.calendarEventId == calendarEventId }
    }

    func listDirty() async throws -> [ReservationEntity] {
        rows.filter { $0.dirtyLocal }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func deleteByLocalId(_ localId: Int64) async throws {
        rows.removeAll { $0.localId == localId }
    }

    func listByCible(cibleType: String, cibleId: String) async throws -> [ReservationEntity] {
        rows.filter { $0.cibleType == cibleType && $0.cibleId == cibleId }
    }

    func listAll() async throws -> [ReservationEntity] {
        rows
    }
}

final class InMemoryIndisponibiliteDao: IndisponibiliteDao {
    private var rows: [IndisponibiliteEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: IndisponibiliteEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> IndisponibiliteEntity? {
        rows.first { $0.serverId == serverId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func listByCible(cibleType: String, cibleId: String) async throws -> [IndisponibiliteEntity] {
        rows.filter { $0.cibleType == cibleType && $0.cibleId == cibleId }
    }

    func listAll() async throws -> [IndisponibiliteEntity] {
        rows
    }
}

final class InMemorySyncMetaDao: SyncMetaDao {
    private var rows: [String: SyncMetaEntity] = [:]

    func upsert(_ entity: SyncMetaEntity) async throws {
        rows[entity.key] = entity
    }

    func get(_ key: String) async throws -> SyncMetaEntity? {
        rows[key]
    }
}

final class InMemorySyncOpDao: SyncOpDao {
    private var rows: [SyncOpEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func insert(_ entity: SyncOpEntity) async throws -> Int64 {
        var row = entity
        if row.id == 0 {
            row.id = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.id == row.id }
            nextId = max(nextId, row.id + 1)
        }
        rows.append(row)
        return row.id
    }

    func listAll() async throws -> [SyncOpEntity] {
        rows.sorted { $0.createdAt < $1.createdAt }
    }

    func getById(_ id: Int64) async throws -> SyncOpEntity? {
        rows.first { $0.id == id }
    }

    func markFailure(id: Int64, error: String?) async throws {
        guard let index = rows.firstIndex(where: { $0.id == id }) else { return }
        rows[index].attempts += 1
        rows[index].lastError = error
    }

    func deleteById(_ id: Int64) async throws {
        rows.removeAll { $0.id == id }
    }
}

final class InMemoryNoteProjetDao: NoteProjetDao {
    private var rows: [NoteProjetEntity] = []
    private var nextId: Int64 = 1

    @discardableResult
    func upsert(_ entity: NoteProjetEntity) async throws -> Int64 {
        var row = entity
        if row.localId == 0 {
            row.localId = nextId
            nextId += 1
        } else {
            rows.removeAll { $0.localId == row.localId }
            nextId = max(nextId, row.localId + 1)
        }
        rows.append(row)
        return row.localId
    }

    func getByServerId(_ serverId: String) async throws -> NoteProjetEntity? {
        rows.first { $0.serverId == serverId }
    }

    func deleteByServerId(_ serverId: String) async throws {
        rows.removeAll { $0.serverId == serverId }
    }

    func deleteByLocalId(_ localId: Int64) async throws {
        rows.removeAll { $0.localId == localId }
    }

    func listByProjetServerId(_ projetServerId: String) async throws -> [NoteProjetEntity] {
        rows.filter { $0.projetServerId == projetServerId }
    }

    func listAll() async throws -> [NoteProjetEntity] {
        rows
    }
}