CrmContactDaoTest.swift
209 lignes · 7510 octets
import XCTest @testable import Card2vcf final class CrmContactDaoTest: XCTestCase { private var db: Card2vcfDatabase! private var dao: (any CrmContactDao)! override func setUp() { super.setUp() db = Card2vcfDatabase(inMemory: true) dao = db.crmContactDao } override func tearDown() { dao = nil db = nil super.tearDown() } private func makeContact( fullName: String? = nil, phones: [String] = [], emails: [String] = [], notes: String? = nil, createdAt: Int64 = 0, serverId: String? = nil ) -> CrmContactEntity { var e = CrmContactEntity() e.fullName = fullName e.phones = phones e.emails = emails e.notes = notes e.createdAt = createdAt e.updatedAt = createdAt e.phonesText = phones.joined(separator: " ") e.emailsText = emails.joined(separator: " ") e.serverId = serverId return e } // MARK: - Round trip func testInsertGetByIdRoundTrip() async throws { var contact = makeContact( fullName: "Jean Dupont", phones: ["+33612345678", "0145678901"], emails: ["jean@exemple.fr"], notes: "rencontré au salon", createdAt: 1000 ) contact.company = "ACME" contact.tags = ["vip", "prospect"] let id = try await dao.insert(contact) XCTAssertGreaterThan(id, 0) var expected = contact expected.id = id let fetched = try await dao.getById(id) XCTAssertEqual(expected, fetched) // INSERT OR REPLACE with an existing id replaces, not duplicates. expected.company = "ACME Corp" let replacedId = try await dao.insert(expected) XCTAssertEqual(id, replacedId) let all = try await dao.getAll() XCTAssertEqual(1, all.count) XCTAssertEqual("ACME Corp", all[0].company) } func testUpdateAndFetchAllOrdering() async throws { let id1 = try await dao.insert(makeContact(fullName: "Ancien", createdAt: 100)) let id3 = try await dao.insert(makeContact(fullName: "Récent", createdAt: 300)) let id2 = try await dao.insert(makeContact(fullName: "Moyen", createdAt: 200)) let all = try await dao.fetchAll() XCTAssertEqual([id3, id2, id1], all.map(\.id)) // createdAt DESC var updated = try await dao.getById(id2)! updated.fullName = "Milieu" updated.updatedAt = 250 try await dao.update(updated) let fetched = try await dao.getById(id2) XCTAssertEqual("Milieu", fetched?.fullName) XCTAssertEqual(250, fetched?.updatedAt) let count = try await dao.fetchAll().count XCTAssertEqual(3, count) } // MARK: - serverId lookups func testServerIdLookups() async throws { let id = try await dao.insert(makeContact(fullName: "Ada", createdAt: 10, serverId: "srv-1")) _ = try await dao.insert(makeContact(fullName: "Grace", createdAt: 20)) let byServer = try await dao.getByServerId("srv-1") XCTAssertEqual(id, byServer?.id) let missing = try await dao.getByServerId("srv-nope") XCTAssertNil(missing) try await dao.deleteByServerId("srv-1") let afterDelete = try await dao.getByServerId("srv-1") XCTAssertNil(afterDelete) let remaining = try await dao.getAll() XCTAssertEqual(1, remaining.count) XCTAssertEqual("Grace", remaining[0].fullName) } // MARK: - FTS search func testSearchIdsByNamePhoneAndNotes() async throws { let jean = try await dao.insert( makeContact( fullName: "Jean Dupont", phones: ["0612345678", "0145678901"], emails: ["jean@exemple.fr"], notes: "rencontré au salon photo", createdAt: 100 ) ) let marie = try await dao.insert( makeContact( fullName: "Marie Curie", emails: ["marie@laboratoire.fr"], notes: "physique nucleaire", createdAt: 200 ) ) // Name fragment, prefix matching (repository appends `*` to each token). let byName = try await dao.searchIds("dup*") XCTAssertEqual([jean], byName) // Case-insensitive. let byNameUpper = try await dao.searchIds("DUPONT*") XCTAssertEqual([jean], byNameUpper) // Multi-token = implicit AND. let byBothTokens = try await dao.searchIds("jean* dupont*") XCTAssertEqual([jean], byBothTokens) let noCrossMatch = try await dao.searchIds("jean* curie*") XCTAssertEqual([], noCrossMatch) // Phone fragment via phonesText. let byPhone = try await dao.searchIds("0612*") XCTAssertEqual([jean], byPhone) let byOtherPhone = try await dao.searchIds("0145678901*") XCTAssertEqual([jean], byOtherPhone) // Note fragment. let byNote = try await dao.searchIds("salon*") XCTAssertEqual([jean], byNote) let byNote2 = try await dao.searchIds("nucleaire*") XCTAssertEqual([marie], byNote2) // Email fragment via emailsText. let byEmail = try await dao.searchIds("laboratoire*") XCTAssertEqual([marie], byEmail) let none = try await dao.searchIds("introuvable*") XCTAssertEqual([], none) } func testSearchIndexFollowsUpdatesAndDeletes() async throws { let id = try await dao.insert(makeContact(fullName: "Jean Dupont", createdAt: 100)) var renamed = try await dao.getById(id)! renamed.fullName = "Albert Einstein" try await dao.update(renamed) let oldName = try await dao.searchIds("dupont*") XCTAssertEqual([], oldName) let newName = try await dao.searchIds("einstein*") XCTAssertEqual([id], newName) // REPLACE by id must reindex too. renamed.fullName = "Isaac Newton" _ = try await dao.insert(renamed) let replacedOld = try await dao.searchIds("einstein*") XCTAssertEqual([], replacedOld) let replacedNew = try await dao.searchIds("newton*") XCTAssertEqual([id], replacedNew) try await dao.deleteById(id) let afterDelete = try await dao.searchIds("newton*") XCTAssertEqual([], afterDelete) } // MARK: - Duplicate exclusions func testExclusionsInsertIgnoreAndDelete() async throws { let first = try await dao.insertExclusion(DuplicateExclusionEntity(idA: 1, idB: 2)) XCTAssertGreaterThan(first, 0) // Same pair again: OnConflictStrategy.IGNORE returns -1. let ignored = try await dao.insertExclusion(DuplicateExclusionEntity(idA: 1, idB: 2)) XCTAssertEqual(-1, ignored) let second = try await dao.insertExclusion(DuplicateExclusionEntity(idA: 2, idB: 3)) XCTAssertGreaterThan(second, 0) let exclusions = try await dao.getExclusions() XCTAssertEqual(2, exclusions.count) try await dao.deleteExclusionsFor(1) let remaining = try await dao.getExclusions() XCTAssertEqual(1, remaining.count) XCTAssertEqual(2, remaining[0].idA) XCTAssertEqual(3, remaining[0].idB) try await dao.deleteExclusionsFor(3) let empty = try await dao.getExclusions() XCTAssertEqual(0, empty.count) } }
GitRust