krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v5.0.0: 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}