summaryrefslogtreecommitdiff
path: root/DomainDigTests/DiffServiceTests.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DomainDigTests/DiffServiceTests.swift')
-rw-r--r--DomainDigTests/DiffServiceTests.swift163
1 files changed, 163 insertions, 0 deletions
diff --git a/DomainDigTests/DiffServiceTests.swift b/DomainDigTests/DiffServiceTests.swift
new file mode 100644
index 0000000..a3a367c
--- /dev/null
+++ b/DomainDigTests/DiffServiceTests.swift
@@ -0,0 +1,163 @@
+import XCTest
+@testable import DomainDig
+
+/// Characterization tests for the field-level diff engine. `DiffService` is a
+/// pure function over two `DomainReport`s, so every case here pins observable
+/// behavior (change classification, normalization, summary phrasing) against a
+/// deterministic fixture.
+final class DiffServiceTests: XCTestCase {
+
+ func testIdenticalReportsProduceNoChanges() {
+ let report = SnapshotFixture.report(
+ availability: .registered,
+ ownership: DomainOwnership(registrar: "Example Registrar")
+ )
+
+ let diff = DiffService.compare(from: report, to: report)
+
+ XCTAssertEqual(diff.changeCount, 0)
+ XCTAssertTrue(diff.changedSectionTitles.isEmpty)
+ XCTAssertFalse(diff.sections.contains { $0.hasChanges })
+ }
+
+ func testAvailabilityTransitionIsReportedAsChanged() {
+ let old = SnapshotFixture.report(availability: .available)
+ let new = SnapshotFixture.report(availability: .registered)
+
+ let diff = DiffService.compare(from: old, to: new)
+
+ let availability = try? XCTUnwrap(diff.sections.first { $0.id == "availability" })
+ let item = availability?.items.first { $0.id == "availability" }
+ XCTAssertEqual(item?.changeType, .changed)
+ XCTAssertEqual(item?.oldValue, "Available")
+ XCTAssertEqual(item?.newValue, "Registered")
+ XCTAssertTrue(diff.changedSectionTitles.contains("Domain / Availability"))
+ }
+
+ func testPrimaryIPChangeSurfacesInAvailabilitySection() {
+ let old = SnapshotFixture.report(
+ availability: .registered,
+ dnsSections: [SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.10"])]
+ )
+ let new = SnapshotFixture.report(
+ availability: .registered,
+ dnsSections: [SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.20"])]
+ )
+
+ let diff = DiffService.compare(from: old, to: new)
+ let item = diff.sections
+ .first { $0.id == "availability" }?
+ .items.first { $0.id == "primary-ip" }
+
+ XCTAssertEqual(item?.changeType, .changed)
+ XCTAssertEqual(item?.oldValue, "203.0.113.10")
+ XCTAssertEqual(item?.newValue, "203.0.113.20")
+ }
+
+ func testCaseAndWhitespaceDifferencesAreNotChanges() {
+ let old = SnapshotFixture.report(ownership: DomainOwnership(registrar: "GoDaddy"))
+ let new = SnapshotFixture.report(ownership: DomainOwnership(registrar: " godaddy "))
+
+ let diff = DiffService.compare(from: old, to: new)
+ let item = diff.sections
+ .first { $0.id == "ownership" }?
+ .items.first { $0.id == "registrar" }
+
+ XCTAssertEqual(item?.changeType, .unchanged, "case- and whitespace-only differences must not register as changes")
+ }
+
+ func testAddedAndRemovedOwnershipFields() {
+ let absent = SnapshotFixture.report(ownership: nil)
+ let present = SnapshotFixture.report(ownership: DomainOwnership(registrar: "Example Registrar"))
+
+ let added = DiffService.compare(from: absent, to: present)
+ .sections.first { $0.id == "ownership" }?
+ .items.first { $0.id == "registrar" }
+ XCTAssertEqual(added?.changeType, .added)
+ XCTAssertNil(added?.oldValue)
+ XCTAssertEqual(added?.newValue, "Example Registrar")
+
+ let removed = DiffService.compare(from: present, to: absent)
+ .sections.first { $0.id == "ownership" }?
+ .items.first { $0.id == "registrar" }
+ XCTAssertEqual(removed?.changeType, .removed)
+ XCTAssertEqual(removed?.oldValue, "Example Registrar")
+ XCTAssertNil(removed?.newValue)
+ }
+
+ func testDNSRecordValueChangeIsDetected() {
+ let old = SnapshotFixture.report(
+ dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["ns1.example.com", "ns2.example.com"])]
+ )
+ let new = SnapshotFixture.report(
+ dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["ns1.example.com", "ns3.example.com"])]
+ )
+
+ let dns = DiffService.compare(from: old, to: new).sections.first { $0.id == "dns" }
+ let records = dns?.items.first { $0.id == "dns-ns-records" }
+ XCTAssertEqual(records?.changeType, .changed)
+ }
+
+ func testDNSRecordReorderIsNotAChange() {
+ // Values are normalized (sorted, lowercased) before comparison, so a pure
+ // reorder must diff as unchanged.
+ let old = SnapshotFixture.report(
+ dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["ns1.example.com", "ns2.example.com"])]
+ )
+ let new = SnapshotFixture.report(
+ dnsSections: [SnapshotFixture.dnsSection(type: .NS, values: ["NS2.example.com", "NS1.example.com"])]
+ )
+
+ let records = DiffService.compare(from: old, to: new)
+ .sections.first { $0.id == "dns" }?
+ .items.first { $0.id == "dns-ns-records" }
+ XCTAssertEqual(records?.changeType, .unchanged)
+ }
+
+ func testSummaryMessagePhrasing() {
+ XCTAssertEqual(DiffService.summaryMessage(from: [], changeCount: 0), "No meaningful changes")
+ XCTAssertEqual(DiffService.summaryMessage(from: ["DNS"], changeCount: 1), "DNS changed")
+ XCTAssertEqual(
+ DiffService.summaryMessage(from: ["DNS", "Ownership"], changeCount: 3),
+ "DNS and ownership changed (3 items)"
+ )
+ }
+
+ func testContextNoteFlagsDifferentResolvers() {
+ let old = SnapshotFixture.report(resolverURLString: "https://one.example/dns-query")
+ let new = SnapshotFixture.report(resolverURLString: "https://two.example/dns-query")
+
+ let note = DiffService.comparisonContextNote(from: old, to: new)
+ XCTAssertEqual(note, "Compared snapshots used different DNS resolvers.")
+ }
+
+ func testContextNoteNilWhenResolversMatch() {
+ let report = SnapshotFixture.report()
+ XCTAssertNil(DiffService.comparisonContextNote(from: report, to: report))
+ }
+
+ func testCertificateWarningLevelThresholds() {
+ func level(daysUntilExpiry days: Int?) -> CertificateWarningLevel {
+ let ssl = days.map { SnapshotFixture.certificate(daysUntilExpiry: $0) }
+ return DiffService.certificateWarningLevel(for: SnapshotFixture.snapshot(sslInfo: ssl))
+ }
+
+ XCTAssertEqual(level(daysUntilExpiry: nil), .none)
+ XCTAssertEqual(level(daysUntilExpiry: 45), .none)
+ XCTAssertEqual(level(daysUntilExpiry: 29), .warning)
+ XCTAssertEqual(level(daysUntilExpiry: 14), .warning)
+ XCTAssertEqual(level(daysUntilExpiry: 13), .critical)
+ XCTAssertEqual(level(daysUntilExpiry: 0), .critical)
+ }
+
+ func testCrossDomainComparisonPairsBothDomains() {
+ let a = SnapshotFixture.report(domain: "alpha.example", availability: .registered)
+ let b = SnapshotFixture.report(domain: "beta.example", availability: .available)
+
+ let result = DiffService.compare(domainA: a, domainB: b)
+
+ XCTAssertEqual(result.domainA, "alpha.example")
+ XCTAssertEqual(result.domainB, "beta.example")
+ XCTAssertTrue(result.changeCount > 0)
+ }
+}