import XCTest @testable import DuooomiBleSDK /// 与 RN `bleStore.upgradeRecords` 行为对齐的关键回归测试。 final class UpgradeRecordStoreTests: XCTestCase { private var defaults: UserDefaults! private let suiteName = "duooomi.test.upgradeRecord" override func setUp() { super.setUp() defaults = UserDefaults(suiteName: suiteName) defaults.removePersistentDomain(forName: suiteName) } override func tearDown() { defaults.removePersistentDomain(forName: suiteName) defaults = nil super.tearDown() } private func makeStore() -> UpgradeRecordStore { UpgradeRecordStore(defaults: defaults) } private func sample(sn: String = "A123456", firmwareId: String = "fw-1", triggeredAt: Int64? = nil) -> UpgradeRecord { UpgradeRecord( sn: sn, firmwareId: firmwareId, fromVersion: "1.0.0", targetVersion: "1.1.0", triggeredAt: triggeredAt ?? UpgradeRecordStore.nowMillis() ) } private func wait(_ s: TimeInterval = 0.1) { let exp = expectation(description: "delay") DispatchQueue.main.asyncAfter(deadline: .now() + s) { exp.fulfill() } wait(for: [exp], timeout: s + 1) } // MARK: - upsert 复合主键 func testUpsertAddsNewRecord() { let store = makeStore() store.upsert(sample()) wait() XCTAssertEqual(store.all.count, 1) XCTAssertEqual(store.all.first?.firmwareId, "fw-1") } func testUpsertSameKeyReplacesAndResetsReportedFlag() { let store = makeStore() let r1 = UpgradeRecord( sn: "A1", firmwareId: "fw-1", fromVersion: "1.0.0", targetVersion: "1.1.0", triggeredAt: 1000, reported: true, reportedAt: 2000, outcome: .success ) store.upsert(r1) wait() // 同 (sn, firmwareId),新 record 覆盖且 reported=false(重试场景) let r2 = UpgradeRecord( sn: "A1", firmwareId: "fw-1", fromVersion: "1.0.0", targetVersion: "1.2.0", triggeredAt: 5000 ) store.upsert(r2) wait() XCTAssertEqual(store.all.count, 1) let only = store.all[0] XCTAssertEqual(only.targetVersion, "1.2.0") XCTAssertFalse(only.reported) XCTAssertNil(only.reportedAt) XCTAssertNil(only.outcome) } func testUpsertDifferentFirmwareIdAppends() { let store = makeStore() store.upsert(sample(firmwareId: "fw-1")) store.upsert(sample(firmwareId: "fw-2")) wait() XCTAssertEqual(store.all.count, 2) } func testUpsertEmptySnIgnored() { let store = makeStore() store.upsert(sample(sn: "")) wait() XCTAssertEqual(store.all.count, 0) } // MARK: - pending 排序 func testPendingExcludesReportedAndSortsAsc() { let store = makeStore() store.upsert(UpgradeRecord(sn: "A", firmwareId: "fw-old", fromVersion: "1.0", targetVersion: "1.1", triggeredAt: 100)) store.upsert(UpgradeRecord(sn: "A", firmwareId: "fw-new", fromVersion: "1.0", targetVersion: "1.2", triggeredAt: 300)) store.upsert(UpgradeRecord(sn: "A", firmwareId: "fw-mid", fromVersion: "1.0", targetVersion: "1.15", triggeredAt: 200)) store.upsert(UpgradeRecord(sn: "B", firmwareId: "fw-other", fromVersion: "1.0", targetVersion: "1.1", triggeredAt: 50)) wait() let pending = store.pending(sn: "A") XCTAssertEqual(pending.map { $0.firmwareId }, ["fw-old", "fw-mid", "fw-new"]) store.markReported(sn: "A", firmwareId: "fw-old", outcome: .success) wait() XCTAssertEqual(store.pending(sn: "A").map { $0.firmwareId }, ["fw-mid", "fw-new"]) } // MARK: - markReported func testMarkReportedSetsFlagAndOutcome() { let store = makeStore() store.upsert(sample()) wait() store.markReported(sn: "A123456", firmwareId: "fw-1", outcome: .versionMismatch) wait() let r = store.all[0] XCTAssertTrue(r.reported) XCTAssertNotNil(r.reportedAt) XCTAssertEqual(r.outcome, .versionMismatch) } func testMarkReportedTtlExpiredOutcomeNil() { // RN 行为:TTL 过期 → markReported(null),保留为审计轨迹 let store = makeStore() store.upsert(sample()) wait() store.markReported(sn: "A123456", firmwareId: "fw-1", outcome: nil) wait() let r = store.all[0] XCTAssertTrue(r.reported) XCTAssertNil(r.outcome) } // MARK: - sn 互斥 func testTryAcquireReturnsFalseWhileHeld() { let store = makeStore() XCTAssertTrue(store.tryAcquire(sn: "A1")) XCTAssertFalse(store.tryAcquire(sn: "A1")) // 不同 sn 互不影响 XCTAssertTrue(store.tryAcquire(sn: "B1")) } func testReleaseAllowsReacquire() { let store = makeStore() XCTAssertTrue(store.tryAcquire(sn: "A1")) store.release(sn: "A1") wait() XCTAssertTrue(store.tryAcquire(sn: "A1")) } // MARK: - 持久化 round-trip func testRecordsPersistAcrossInstances() { let store1 = makeStore() store1.upsert(sample()) wait() let store2 = UpgradeRecordStore(defaults: defaults) XCTAssertEqual(store2.all.count, 1) XCTAssertEqual(store2.all[0].firmwareId, "fw-1") } }