diff options
| author | Christian Cleberg <[email protected]> | 2026-07-24 19:27:24 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-24 22:52:53 -0500 |
| commit | a7d154e1db9fd9904b2255db889144ad15aac2f2 (patch) | |
| tree | 36e9d636821952ca1a4a0b2d072065687abc1960 /DomainDigTests/DiffServiceTests.swift | |
| parent | 22ad96331a4456662f740c48882cab8aadd6d0b4 (diff) | |
| download | domain-dig-a7d154e1db9fd9904b2255db889144ad15aac2f2.tar.gz domain-dig-a7d154e1db9fd9904b2255db889144ad15aac2f2.tar.bz2 domain-dig-a7d154e1db9fd9904b2255db889144ad15aac2f2.zip | |
test: establish unit-test net for the deterministic core (v5 step 1)
Stands up the DomainDigTests unit-test target the v5.0.0 roadmap flags as the
mandatory first move before decomposing the god-files and locking external
contracts. The project had no XCTest unit target — only the DomainDigUITests
accessibility suite.
- New DomainDigTests target (unit_test_bundle), hosted by the app so
@testable import DomainDig links. Mirrors the UITests build settings
(SWIFT_VERSION 5.0 + MainActor default isolation) to avoid the XCTest
override-isolation issue recorded for the Swift 6 targets. Wired into the
shared DomainDig scheme's Test action, so it runs in CI and the pre-push
audit hook automatically (both invoke the whole scheme).
- SnapshotFixture builds the deep LookupSnapshot/DomainReport models through
their real initializer and the DomainReportBuilder, exposing only the fields
the tests vary.
- 36 characterization tests across the four deterministic units the roadmap
names:
- DiffService: change classification, case/whitespace normalization, DNS
record reorder-vs-change, summary phrasing, resolver context note,
certificate-warning thresholds.
- DomainReportBuilder: snapshot -> report field mapping, primary-IP and
DNSSEC derivation, TLS status, partial-snapshot/validation passthrough.
- DomainReportExporter: format dispatch, JSON round-trip (the machine
contract), CSV/markdown/text/PDF structural invariants, timeline.
- DomainDataPortabilityService: CSV round-trip and the merge/dedup semantics
(case-insensitive collapse, OR-merged pin state, min-created/max-updated,
recency sort) via an ephemeral UserDefaults suite.
- build.yml comment updated to reflect the second test target.
Diffstat (limited to 'DomainDigTests/DiffServiceTests.swift')
| -rw-r--r-- | DomainDigTests/DiffServiceTests.swift | 163 |
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) + } +} |
