summaryrefslogtreecommitdiff
path: root/DomainDigTests/DomainReportExporterTests.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/DomainReportExporterTests.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/DomainReportExporterTests.swift')
-rw-r--r--DomainDigTests/DomainReportExporterTests.swift107
1 files changed, 107 insertions, 0 deletions
diff --git a/DomainDigTests/DomainReportExporterTests.swift b/DomainDigTests/DomainReportExporterTests.swift
new file mode 100644
index 0000000..ddecabf
--- /dev/null
+++ b/DomainDigTests/DomainReportExporterTests.swift
@@ -0,0 +1,107 @@
+import XCTest
+@testable import DomainDig
+
+/// Characterization tests for the export surface. These pin the format dispatch,
+/// the JSON round-trip (the canonical machine contract), and the structural
+/// invariants of the human-readable formats.
+final class DomainReportExporterTests: XCTestCase {
+
+ private let decoder: JSONDecoder = {
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = .iso8601
+ return decoder
+ }()
+
+ func testEveryFormatProducesNonEmptyData() throws {
+ let report = SnapshotFixture.report(availability: .registered)
+ for format in DomainExportFormat.allCases {
+ let data = try DomainReportExporter.data(for: report, format: format)
+ XCTAssertFalse(data.isEmpty, "\(format.rawValue) export was empty")
+ }
+ }
+
+ func testJSONExportRoundTripsToReport() throws {
+ let report = SnapshotFixture.report(domain: "roundtrip.example", availability: .registered)
+
+ let data = try DomainReportExporter.data(for: report, format: .json)
+ let decoded = try decoder.decode(DomainReport.self, from: data)
+
+ XCTAssertEqual(decoded.domain, "roundtrip.example")
+ XCTAssertEqual(decoded.availability, .registered)
+ XCTAssertEqual(decoded.timestamp, report.timestamp)
+ }
+
+ func testBatchJSONExportRoundTripsToReportArray() throws {
+ let reports = [
+ SnapshotFixture.report(domain: "one.example"),
+ SnapshotFixture.report(domain: "two.example")
+ ]
+
+ let data = try DomainReportExporter.data(for: reports, format: .json, title: "Batch")
+ let decoded = try decoder.decode([DomainReport].self, from: data)
+
+ XCTAssertEqual(decoded.map(\.domain), ["one.example", "two.example"])
+ }
+
+ func testCSVHasHeaderAndOneRowPerReport() {
+ let reports = [
+ SnapshotFixture.report(domain: "alpha.example"),
+ SnapshotFixture.report(domain: "beta.example")
+ ]
+
+ let csv = DomainReportExporter.csv(for: reports)
+ let lines = csv.split(separator: "\n", omittingEmptySubsequences: false)
+
+ XCTAssertEqual(lines.count, 3, "one header line plus one row per report")
+ XCTAssertTrue(lines[0].contains("\"domain\""))
+ XCTAssertTrue(csv.contains("\"alpha.example\""))
+ XCTAssertTrue(csv.contains("\"beta.example\""))
+ }
+
+ func testMarkdownIsHeadedAndNamesTheDomain() {
+ let markdown = DomainReportExporter.markdown(for: SnapshotFixture.report(domain: "md.example"))
+
+ XCTAssertTrue(markdown.hasPrefix("# "), "markdown export should open with an H1")
+ XCTAssertTrue(markdown.contains("md.example"))
+ }
+
+ func testTextExportNamesTheDomain() {
+ let text = DomainReportExporter.text(for: SnapshotFixture.report(domain: "txt.example"))
+ XCTAssertTrue(text.contains("txt.example"))
+ }
+
+ func testBatchExportsCarryTitleAndEveryDomain() {
+ let reports = [
+ SnapshotFixture.report(domain: "first.example"),
+ SnapshotFixture.report(domain: "second.example")
+ ]
+
+ let markdown = DomainReportExporter.batchMarkdown(for: reports, title: "Portfolio Sweep")
+ XCTAssertTrue(markdown.contains("Portfolio Sweep"))
+ XCTAssertTrue(markdown.contains("first.example"))
+ XCTAssertTrue(markdown.contains("second.example"))
+
+ let text = DomainReportExporter.batchText(for: reports, title: "Portfolio Sweep")
+ XCTAssertTrue(text.contains("Portfolio Sweep"))
+ XCTAssertTrue(text.contains("first.example"))
+ XCTAssertTrue(text.contains("second.example"))
+ }
+
+ func testPDFExportHasPDFSignature() throws {
+ let data = try DomainReportExporter.data(for: SnapshotFixture.report(), format: .pdf)
+ XCTAssertTrue(data.starts(with: Array("%PDF".utf8)), "PDF export should begin with the %PDF signature")
+ }
+
+ func testTimelineTextNamesTheDomain() {
+ let reports = [
+ SnapshotFixture.report(domain: "timeline.example", timestamp: SnapshotFixture.referenceDate),
+ SnapshotFixture.report(
+ domain: "timeline.example",
+ timestamp: SnapshotFixture.referenceDate.addingTimeInterval(86_400)
+ )
+ ]
+
+ let text = DomainReportExporter.timelineText(for: reports, domain: "timeline.example", includeDiffSummary: false)
+ XCTAssertTrue(text.contains("timeline.example"))
+ }
+}