From a7d154e1db9fd9904b2255db889144ad15aac2f2 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 24 Jul 2026 19:27:24 -0500 Subject: test: establish unit-test net for the deterministic core (v5 step 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- DomainDigTests/DomainReportBuilderTests.swift | 130 ++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 DomainDigTests/DomainReportBuilderTests.swift (limited to 'DomainDigTests/DomainReportBuilderTests.swift') 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]) + } +} -- cgit v1.2.3