krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

main: DomainDigTests/DomainDataPortabilityServiceTests.swift · raw

  1import XCTest
  2@testable import DomainDig
  3
  4/// Characterization tests for the portability layer's deterministic core: the
  5/// CSV round-trip and the merge/dedup semantics that `load*/save*` apply. Storage
  6/// is exercised through an ephemeral `UserDefaults` suite so nothing touches the
  7/// real app domain.
  8final class DomainDataPortabilityServiceTests: XCTestCase {
  9    private let suiteName = "DomainDigTests.portability"
 10    private var defaults: UserDefaults!
 11    private let base = Date(timeIntervalSince1970: 1_700_000_000)
 12
 13    override func setUp() {
 14        super.setUp()
 15        defaults = UserDefaults(suiteName: suiteName)
 16        defaults.removePersistentDomain(forName: suiteName)
 17    }
 18
 19    override func tearDown() {
 20        defaults.removePersistentDomain(forName: suiteName)
 21        defaults = nil
 22        super.tearDown()
 23    }
 24
 25    // MARK: - CSV round-trip
 26
 27    func testTrackedDomainCSVRoundTripPreservesFields() throws {
 28        let original = TrackedDomain(
 29            domain: "csv.example",
 30            createdAt: base,
 31            updatedAt: base.addingTimeInterval(3_600),
 32            note: "keep an eye on this",
 33            isPinned: true,
 34            monitoringEnabled: false,
 35            lastKnownAvailability: .registered,
 36            certificateWarningLevel: .warning,
 37            certificateDaysRemaining: 12
 38        )
 39
 40        let csv = DataPortabilityCSV.trackedDomains([original])
 41        let parsed = try DataPortabilityCSV.parseTrackedDomains(from: csv)
 42
 43        XCTAssertEqual(parsed.count, 1)
 44        let restored = try XCTUnwrap(parsed.first)
 45        XCTAssertEqual(restored.domain, "csv.example")
 46        XCTAssertEqual(restored.note, "keep an eye on this")
 47        XCTAssertTrue(restored.isPinned)
 48        XCTAssertFalse(restored.monitoringEnabled)
 49        XCTAssertEqual(restored.lastKnownAvailability, .registered)
 50        XCTAssertEqual(restored.certificateWarningLevel, .warning)
 51        XCTAssertEqual(restored.certificateDaysRemaining, 12)
 52    }
 53
 54    func testTrackedDomainCSVParseSkipsRowsWithoutDomain() throws {
 55        let csv = """
 56        domain,isPinned,monitoringEnabled
 57        ,true,true
 58        valid.example,false,true
 59        """
 60
 61        let parsed = try DataPortabilityCSV.parseTrackedDomains(from: csv)
 62
 63        XCTAssertEqual(parsed.map(\.domain), ["valid.example"])
 64    }
 65
 66    // MARK: - Merge / dedup
 67
 68    func testSaveLoadDeduplicatesSameDomainCaseInsensitively() {
 69        let older = TrackedDomain(
 70            domain: "example.com",
 71            createdAt: base.addingTimeInterval(-100),
 72            updatedAt: base.addingTimeInterval(-50),
 73            isPinned: false,
 74            monitoringEnabled: false
 75        )
 76        let newer = TrackedDomain(
 77            domain: "EXAMPLE.com",
 78            createdAt: base.addingTimeInterval(-80),
 79            updatedAt: base.addingTimeInterval(-10),
 80            isPinned: true,
 81            monitoringEnabled: false
 82        )
 83
 84        DomainDataPortabilityService.saveTrackedDomains([older, newer], defaults: defaults)
 85        let loaded = DomainDataPortabilityService.loadTrackedDomains(defaults: defaults)
 86
 87        XCTAssertEqual(loaded.count, 1, "same domain differing only in case must collapse to one entry")
 88        let merged = loaded[0]
 89        XCTAssertEqual(merged.domain, "example.com")
 90        XCTAssertTrue(merged.isPinned, "pinned state is OR-merged")
 91        XCTAssertFalse(merged.monitoringEnabled)
 92        XCTAssertEqual(merged.createdAt, base.addingTimeInterval(-100), "createdAt takes the earliest")
 93        XCTAssertEqual(merged.updatedAt, base.addingTimeInterval(-10), "updatedAt takes the latest")
 94    }
 95
 96    func testSaveLoadKeepsDistinctDomainsSortedByRecency() {
 97        let domains = [
 98            TrackedDomain(domain: "old.example", updatedAt: base.addingTimeInterval(-300)),
 99            TrackedDomain(domain: "new.example", updatedAt: base.addingTimeInterval(-10)),
100            TrackedDomain(domain: "mid.example", updatedAt: base.addingTimeInterval(-100))
101        ]
102
103        DomainDataPortabilityService.saveTrackedDomains(domains, defaults: defaults)
104        let loaded = DomainDataPortabilityService.loadTrackedDomains(defaults: defaults)
105
106        XCTAssertEqual(loaded.map(\.domain), ["new.example", "mid.example", "old.example"])
107    }
108
109    func testLoadTrackedDomainsEmptyWhenUnset() {
110        XCTAssertTrue(DomainDataPortabilityService.loadTrackedDomains(defaults: defaults).isEmpty)
111    }
112
113    // MARK: - Recent searches
114
115    func testRecentSearchesRoundTripAndTruncateToTwenty() {
116        let values = (0..<30).map { "domain\($0).example" }
117
118        DomainDataPortabilityService.saveRecentSearches(values, defaults: defaults)
119        let loaded = DomainDataPortabilityService.loadRecentSearches(defaults: defaults)
120
121        XCTAssertEqual(loaded.count, 20, "recent searches are capped at 20")
122        XCTAssertEqual(loaded, Array(values.prefix(20)), "order is preserved")
123    }
124}