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/Fixtures/SnapshotFixture.swift | 191 ++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 DomainDigTests/Fixtures/SnapshotFixture.swift (limited to 'DomainDigTests/Fixtures/SnapshotFixture.swift') 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 + ) + } +} -- cgit v1.2.3