summaryrefslogtreecommitdiff
path: root/DomainDigTests/Fixtures
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/Fixtures
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/Fixtures')
-rw-r--r--DomainDigTests/Fixtures/SnapshotFixture.swift191
1 files changed, 191 insertions, 0 deletions
diff --git a/DomainDigTests/Fixtures/SnapshotFixture.swift b/DomainDigTests/Fixtures/SnapshotFixture.swift
new file mode 100644
index 0000000..4129a30
--- /dev/null
+++ b/DomainDigTests/Fixtures/SnapshotFixture.swift
@@ -0,0 +1,191 @@
+import Foundation
+@testable import DomainDig
+
+/// Deterministic builders for the deep `LookupSnapshot` / `DomainReport` models
+/// so the core unit tests can construct inputs without wiring up every one of the
+/// ~75 snapshot fields at each call site.
+///
+/// `snapshot(...)` exposes only the fields the tests actually vary; everything
+/// else defaults to an empty/absent value. `report(...)` runs a snapshot through
+/// the real `DomainReportBuilder`, which is both the natural constructor for the
+/// otherwise-unconstructable `DomainReport` and, for the builder tests, the unit
+/// under test.
+enum SnapshotFixture {
+ /// Fixed instant so timestamp-derived output (diff ranges, export headers) is
+ /// stable across runs.
+ static let referenceDate = Date(timeIntervalSince1970: 1_700_000_000)
+
+ static let defaultResolverURL = "https://resolver.example/dns-query"
+
+ static func snapshot(
+ domain: String = "example.com",
+ timestamp: Date = referenceDate,
+ resolverURLString: String = defaultResolverURL,
+ resultSource: LookupResultSource = .live,
+ availability: DomainAvailabilityStatus? = nil,
+ ownership: DomainOwnership? = nil,
+ dnsSections: [DNSSection] = [],
+ ptrRecord: String? = nil,
+ sslInfo: SSLCertificateInfo? = nil,
+ httpHeaders: [HTTPHeader] = [],
+ httpStatusCode: Int? = nil,
+ httpSecurityGrade: String? = nil,
+ subdomains: [DiscoveredSubdomain] = [],
+ extendedSubdomains: [DiscoveredSubdomain] = [],
+ isPartialSnapshot: Bool = false,
+ validationIssues: [String] = [],
+ changeSummary: DomainChangeSummary? = nil
+ ) -> LookupSnapshot {
+ LookupSnapshot(
+ historyEntryID: nil,
+ domain: domain,
+ timestamp: timestamp,
+ trackedDomainID: nil,
+ note: nil,
+ appVersion: "test",
+ resolverDisplayName: "Test Resolver",
+ resolverURLString: resolverURLString,
+ dataSources: [],
+ provenanceBySection: [:],
+ availabilityConfidence: nil,
+ ownershipConfidence: nil,
+ subdomainConfidence: nil,
+ emailSecurityConfidence: nil,
+ geolocationConfidence: nil,
+ errorDetails: [:],
+ isPartialSnapshot: isPartialSnapshot,
+ validationIssues: validationIssues,
+ totalLookupDurationMs: nil,
+ snapshotIndex: nil,
+ previousSnapshotID: nil,
+ changeCount: 0,
+ severitySummary: nil,
+ dnsSections: dnsSections,
+ dnsError: nil,
+ availabilityResult: availability.map { DomainAvailabilityResult(domain: domain, status: $0) },
+ suggestions: [],
+ sslInfo: sslInfo,
+ sslError: nil,
+ hstsPreloaded: nil,
+ httpHeaders: httpHeaders,
+ httpSecurityGrade: httpSecurityGrade,
+ httpStatusCode: httpStatusCode,
+ httpResponseTimeMs: nil,
+ httpProtocol: nil,
+ http3Advertised: false,
+ httpHeadersError: nil,
+ reachabilityResults: [],
+ reachabilityError: nil,
+ ipGeolocation: nil,
+ ipGeolocationError: nil,
+ emailSecurity: nil,
+ emailSecurityError: nil,
+ ownership: ownership,
+ ownershipError: nil,
+ ownershipHistory: [],
+ ownershipHistoryError: nil,
+ inferredProvider: nil,
+ priorProviders: [],
+ domainClassification: nil,
+ ownershipTransitions: [],
+ hostingTransitions: [],
+ subdomainHistory: [],
+ riskSignals: [],
+ intelligenceTimeline: [],
+ ptrRecord: ptrRecord,
+ ptrError: nil,
+ redirectChain: [],
+ redirectChainError: nil,
+ subdomains: subdomains,
+ subdomainsError: nil,
+ extendedSubdomains: extendedSubdomains,
+ extendedSubdomainsError: nil,
+ dnsHistory: [],
+ dnsHistoryError: nil,
+ domainPricing: nil,
+ domainPricingError: nil,
+ reputation: nil,
+ reputationError: nil,
+ portScanResults: [],
+ portScanError: nil,
+ changeSummary: changeSummary,
+ resultSource: resultSource,
+ cachedSections: [],
+ statusMessage: nil
+ )
+ }
+
+ static func report(
+ domain: String = "example.com",
+ timestamp: Date = referenceDate,
+ resolverURLString: String = defaultResolverURL,
+ resultSource: LookupResultSource = .live,
+ availability: DomainAvailabilityStatus? = nil,
+ ownership: DomainOwnership? = nil,
+ dnsSections: [DNSSection] = [],
+ ptrRecord: String? = nil,
+ sslInfo: SSLCertificateInfo? = nil,
+ httpHeaders: [HTTPHeader] = [],
+ httpStatusCode: Int? = nil,
+ httpSecurityGrade: String? = nil,
+ subdomains: [DiscoveredSubdomain] = [],
+ extendedSubdomains: [DiscoveredSubdomain] = [],
+ isPartialSnapshot: Bool = false,
+ validationIssues: [String] = []
+ ) -> DomainReport {
+ DomainReportBuilder().build(
+ from: snapshot(
+ domain: domain,
+ timestamp: timestamp,
+ resolverURLString: resolverURLString,
+ resultSource: resultSource,
+ availability: availability,
+ ownership: ownership,
+ dnsSections: dnsSections,
+ ptrRecord: ptrRecord,
+ sslInfo: sslInfo,
+ httpHeaders: httpHeaders,
+ httpStatusCode: httpStatusCode,
+ httpSecurityGrade: httpSecurityGrade,
+ subdomains: subdomains,
+ extendedSubdomains: extendedSubdomains,
+ isPartialSnapshot: isPartialSnapshot,
+ validationIssues: validationIssues
+ ),
+ deriveChangeSummary: false
+ )
+ }
+
+ // MARK: - Nested model conveniences
+
+ static func dnsSection(
+ type: DNSRecordType,
+ values: [String],
+ ttl: Int = 300,
+ dnssecSigned: Bool? = nil,
+ wildcards: [String] = []
+ ) -> DNSSection {
+ DNSSection(
+ recordType: type,
+ records: values.map { DNSRecord(value: $0, ttl: ttl) },
+ wildcardRecords: wildcards.map { DNSRecord(value: $0, ttl: ttl) },
+ dnssecSigned: dnssecSigned
+ )
+ }
+
+ static func certificate(
+ commonName: String = "example.com",
+ issuer: String = "Test CA",
+ daysUntilExpiry: Int = 90
+ ) -> SSLCertificateInfo {
+ SSLCertificateInfo(
+ commonName: commonName,
+ subjectAltNames: [commonName],
+ issuer: issuer,
+ validFrom: referenceDate,
+ validUntil: referenceDate.addingTimeInterval(Double(daysUntilExpiry) * 86_400),
+ daysUntilExpiry: daysUntilExpiry,
+ chainDepth: 1
+ )
+ }
+}