summaryrefslogtreecommitdiff
path: root/DomainDigTests/DomainReportBuilderTests.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-24 19:27:24 -0500
committerChristian Cleberg <[email protected]>2026-07-24 22:52:53 -0500
commita7d154e1db9fd9904b2255db889144ad15aac2f2 (patch)
tree36e9d636821952ca1a4a0b2d072065687abc1960 /DomainDigTests/DomainReportBuilderTests.swift
parent22ad96331a4456662f740c48882cab8aadd6d0b4 (diff)
downloaddomain-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/DomainReportBuilderTests.swift')
-rw-r--r--DomainDigTests/DomainReportBuilderTests.swift130
1 files changed, 130 insertions, 0 deletions
diff --git a/DomainDigTests/DomainReportBuilderTests.swift b/DomainDigTests/DomainReportBuilderTests.swift
new file mode 100644
index 0000000..7c8db15
--- /dev/null
+++ b/DomainDigTests/DomainReportBuilderTests.swift
@@ -0,0 +1,130 @@
+import XCTest
+@testable import DomainDig
+
+/// Characterization tests for the snapshot → report projection. These lock the
+/// field-mapping and derivation rules the export/diff contracts depend on.
+final class DomainReportBuilderTests: XCTestCase {
+ private let builder = DomainReportBuilder()
+
+ func testCoreIdentityFieldsPassThrough() {
+ let report = builder.build(
+ from: SnapshotFixture.snapshot(
+ domain: "mapped.example",
+ resolverURLString: "https://r.example/dns-query"
+ ),
+ deriveChangeSummary: false
+ )
+
+ XCTAssertEqual(report.domain, "mapped.example")
+ XCTAssertEqual(report.timestamp, SnapshotFixture.referenceDate)
+ XCTAssertEqual(report.resolverURLString, "https://r.example/dns-query")
+ XCTAssertFalse(report.metadata.schemaVersion.isEmpty)
+ }
+
+ func testAvailabilityDefaultsToUnknownWhenAbsent() {
+ let unknown = builder.build(from: SnapshotFixture.snapshot(availability: nil), deriveChangeSummary: false)
+ XCTAssertEqual(unknown.availability, .unknown)
+
+ let registered = builder.build(from: SnapshotFixture.snapshot(availability: .registered), deriveChangeSummary: false)
+ XCTAssertEqual(registered.availability, .registered)
+ }
+
+ func testPrimaryIPComesFromFirstARecord() {
+ let report = builder.build(
+ from: SnapshotFixture.snapshot(
+ dnsSections: [
+ SnapshotFixture.dnsSection(type: .A, values: ["198.51.100.7", "198.51.100.8"]),
+ SnapshotFixture.dnsSection(type: .AAAA, values: ["2001:db8::1"])
+ ]
+ ),
+ deriveChangeSummary: false
+ )
+
+ XCTAssertEqual(report.dns.primaryIP, "198.51.100.7")
+ XCTAssertEqual(report.network.primaryIP, "198.51.100.7")
+ }
+
+ func testPrimaryIPNilWithoutARecord() {
+ let report = builder.build(
+ from: SnapshotFixture.snapshot(
+ dnsSections: [SnapshotFixture.dnsSection(type: .MX, values: ["mail.example.com"])]
+ ),
+ deriveChangeSummary: false
+ )
+ XCTAssertNil(report.dns.primaryIP)
+ }
+
+ func testDNSSECDerivedFromSections() {
+ let signed = builder.build(
+ from: SnapshotFixture.snapshot(
+ dnsSections: [SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.1"], dnssecSigned: true)]
+ ),
+ deriveChangeSummary: false
+ )
+ XCTAssertEqual(signed.dns.dnssecSigned, true)
+
+ let unknown = builder.build(
+ from: SnapshotFixture.snapshot(
+ dnsSections: [SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.1"])]
+ ),
+ deriveChangeSummary: false
+ )
+ XCTAssertNil(unknown.dns.dnssecSigned)
+ }
+
+ func testTLSStatusReflectsCertificatePresence() {
+ let valid = builder.build(
+ from: SnapshotFixture.snapshot(sslInfo: SnapshotFixture.certificate()),
+ deriveChangeSummary: false
+ )
+ XCTAssertEqual(valid.web.tlsStatus, "valid")
+
+ let missing = builder.build(from: SnapshotFixture.snapshot(sslInfo: nil), deriveChangeSummary: false)
+ XCTAssertEqual(missing.web.tlsStatus, "unavailable")
+ }
+
+ func testWebHeaderCountAndFinalURL() {
+ let report = builder.build(
+ from: SnapshotFixture.snapshot(
+ httpHeaders: [
+ HTTPHeader(name: "Content-Type", value: "text/html"),
+ HTTPHeader(name: "Strict-Transport-Security", value: "max-age=63072000")
+ ],
+ httpStatusCode: 200
+ ),
+ deriveChangeSummary: false
+ )
+
+ XCTAssertEqual(report.web.headerCount, 2)
+ XCTAssertEqual(report.web.statusCode, 200)
+ XCTAssertNil(report.web.finalURL, "no redirect chain means no final URL")
+ }
+
+ func testPartialSnapshotAndValidationIssuesPropagate() {
+ let report = builder.build(
+ from: SnapshotFixture.snapshot(
+ isPartialSnapshot: true,
+ validationIssues: ["missing DNS", "stale WHOIS"]
+ ),
+ deriveChangeSummary: false
+ )
+
+ XCTAssertTrue(report.isPartialSnapshot)
+ XCTAssertEqual(report.validationIssues, ["missing DNS", "stale WHOIS"])
+ XCTAssertTrue(report.metadata.isPartialSnapshot)
+ XCTAssertEqual(report.metadata.validationIssues, ["missing DNS", "stale WHOIS"])
+ }
+
+ func testRecordSectionsArePreserved() {
+ let sections = [
+ SnapshotFixture.dnsSection(type: .A, values: ["203.0.113.1"]),
+ SnapshotFixture.dnsSection(type: .MX, values: ["mail.example.com"])
+ ]
+ let report = builder.build(
+ from: SnapshotFixture.snapshot(dnsSections: sections),
+ deriveChangeSummary: false
+ )
+
+ XCTAssertEqual(Set(report.dns.recordSections.map(\.recordType)), [.A, .MX])
+ }
+}