krz/domain-dig

an ios app for DNS & SSL analysis

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

v5.0.2: DomainDigTests/DiffServiceTests.swift · raw

  1import XCTest
  2@testable import DomainDig
  3
  4/// Characterization tests for the field-level diff engine. `DiffService` is a
  5/// pure function over two `DomainReport`s, so every case here pins observable
  6/// behavior (change classification, normalization, summary phrasing) against a
  7/// deterministic fixture.
  8final class DiffServiceTests: XCTestCase {
  9
 10    func testIdenticalReportsProduceNoChanges() {
 11        let report = SnapshotFixture.report(
 12            availability: .registered,
 13            ownership: DomainOwnership(registrar: "Example Registrar")
 14        )
 15
 16        let diff = DiffService.compare(from: report, to: report)
 17
 18        XCTAssertEqual(diff.changeCount, 0)
 19        XCTAssertTrue(diff.changedSectionTitles.isEmpty)
 20        XCTAssertFalse(diff.sections.contains { $0.hasChanges })
 21    }
 22
 23    func testAvailabilityTransitionIsReportedAsChanged() {
 24        let old = SnapshotFixture.report(availability: .available)
 25        let new = SnapshotFixture.report(availability: .registered)
 26
 27        let diff = DiffService.compare(from: old, to: new)
 28
 29        let availability = try? XCTUnwrap(diff.sections.first { $0.id == "availability" })
 30        let item = availability?.items.first { $0.id == "availability" }
 31        XCTAssertEqual(item?.changeType, .changed)
 32        XCTAssertEqual(item?.oldValue, "Available")
 33        XCTAssertEqual(item?.newValue, "Registered")
 34        XCTAssertTrue(diff.changedSectionTitles.contains("Domain / Availability"))
 35    }
 36
 37    func testPrimaryIPChangeSurfacesInAvailabilitySection() {
 38        let old = SnapshotFixture.report(
 39            availability: .registered,
 40            dnsSections: [SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.10"])]
 41        )
 42        let new = SnapshotFixture.report(
 43            availability: .registered,
 44            dnsSections: [SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.20"])]
 45        )
 46
 47        let diff = DiffService.compare(from: old, to: new)
 48        let item = diff.sections
 49            .first { $0.id == "availability" }?
 50            .items.first { $0.id == "primary-ip" }
 51
 52        XCTAssertEqual(item?.changeType, .changed)
 53        XCTAssertEqual(item?.oldValue, "203.0.113.10")
 54        XCTAssertEqual(item?.newValue, "203.0.113.20")
 55    }
 56
 57    func testCaseAndWhitespaceDifferencesAreNotChanges() {
 58        let old = SnapshotFixture.report(ownership: DomainOwnership(registrar: "GoDaddy"))
 59        let new = SnapshotFixture.report(ownership: DomainOwnership(registrar: "  godaddy  "))
 60
 61        let diff = DiffService.compare(from: old, to: new)
 62        let item = diff.sections
 63            .first { $0.id == "ownership" }?
 64            .items.first { $0.id == "registrar" }
 65
 66        XCTAssertEqual(item?.changeType, .unchanged, "case- and whitespace-only differences must not register as changes")
 67    }
 68
 69    func testAddedAndRemovedOwnershipFields() {
 70        let absent = SnapshotFixture.report(ownership: nil)
 71        let present = SnapshotFixture.report(ownership: DomainOwnership(registrar: "Example Registrar"))
 72
 73        let added = DiffService.compare(from: absent, to: present)
 74            .sections.first { $0.id == "ownership" }?
 75            .items.first { $0.id == "registrar" }
 76        XCTAssertEqual(added?.changeType, .added)
 77        XCTAssertNil(added?.oldValue)
 78        XCTAssertEqual(added?.newValue, "Example Registrar")
 79
 80        let removed = DiffService.compare(from: present, to: absent)
 81            .sections.first { $0.id == "ownership" }?
 82            .items.first { $0.id == "registrar" }
 83        XCTAssertEqual(removed?.changeType, .removed)
 84        XCTAssertEqual(removed?.oldValue, "Example Registrar")
 85        XCTAssertNil(removed?.newValue)
 86    }
 87
 88    func testDNSRecordValueChangeIsDetected() {
 89        let old = SnapshotFixture.report(
 90            dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["ns1.example.com", "ns2.example.com"])]
 91        )
 92        let new = SnapshotFixture.report(
 93            dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["ns1.example.com", "ns3.example.com"])]
 94        )
 95
 96        let dns = DiffService.compare(from: old, to: new).sections.first { $0.id == "dns" }
 97        let records = dns?.items.first { $0.id == "dns-ns-records" }
 98        XCTAssertEqual(records?.changeType, .changed)
 99    }
100
101    func testDNSRecordReorderIsNotAChange() {
102        // Values are normalized (sorted, lowercased) before comparison, so a pure
103        // reorder must diff as unchanged.
104        let old = SnapshotFixture.report(
105            dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["ns1.example.com", "ns2.example.com"])]
106        )
107        let new = SnapshotFixture.report(
108            dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["NS2.example.com", "NS1.example.com"])]
109        )
110
111        let records = DiffService.compare(from: old, to: new)
112            .sections.first { $0.id == "dns" }?
113            .items.first { $0.id == "dns-ns-records" }
114        XCTAssertEqual(records?.changeType, .unchanged)
115    }
116
117    func testSummaryMessagePhrasing() {
118        XCTAssertEqual(DiffService.summaryMessage(from: [], changeCount: 0), "No meaningful changes")
119        XCTAssertEqual(DiffService.summaryMessage(from: ["DNS"], changeCount: 1), "DNS changed")
120        XCTAssertEqual(
121            DiffService.summaryMessage(from: ["DNS", "Ownership"], changeCount: 3),
122            "DNS and ownership changed (3 items)"
123        )
124    }
125
126    func testContextNoteFlagsDifferentResolvers() {
127        let old = SnapshotFixture.report(resolverURLString: "https://one.example/dns-query")
128        let new = SnapshotFixture.report(resolverURLString: "https://two.example/dns-query")
129
130        let note = DiffService.comparisonContextNote(from: old, to: new)
131        XCTAssertEqual(note, "Compared snapshots used different DNS resolvers.")
132    }
133
134    func testContextNoteNilWhenResolversMatch() {
135        let report = SnapshotFixture.report()
136        XCTAssertNil(DiffService.comparisonContextNote(from: report, to: report))
137    }
138
139    func testCertificateWarningLevelThresholds() {
140        func level(daysUntilExpiry days: Int?) -> CertificateWarningLevel {
141            let ssl = days.map { SnapshotFixture.certificate(daysUntilExpiry: $0) }
142            return DiffService.certificateWarningLevel(for: SnapshotFixture.snapshot(sslInfo: ssl))
143        }
144
145        XCTAssertEqual(level(daysUntilExpiry: nil), .none)
146        XCTAssertEqual(level(daysUntilExpiry: 45), .none)
147        XCTAssertEqual(level(daysUntilExpiry: 29), .warning)
148        XCTAssertEqual(level(daysUntilExpiry: 14), .warning)
149        XCTAssertEqual(level(daysUntilExpiry: 13), .critical)
150        XCTAssertEqual(level(daysUntilExpiry: 0), .critical)
151    }
152
153    func testCrossDomainComparisonPairsBothDomains() {
154        let a = SnapshotFixture.report(domain: "alpha.example", availability: .registered)
155        let b = SnapshotFixture.report(domain: "beta.example", availability: .available)
156
157        let result = DiffService.compare(domainA: a, domainB: b)
158
159        XCTAssertEqual(result.domainA, "alpha.example")
160        XCTAssertEqual(result.domainB, "beta.example")
161        XCTAssertTrue(result.changeCount > 0)
162    }
163}