Files
Flights/FlightsTests/AirframeHistoryStoreTests.swift
T
Trey T ba0688a412 Search: FlightAware backbone, blob catalog, diagnostic infra
route-explorer's /api/token sits behind invisible Cloudflare Turnstile
that requires Apple's Private Access Token attestation. Third-party
iOS apps don't qualify for PAT issuance, and Linux Docker containers
can't pass it either (cross-OS fingerprint, even with patchright /
Camoufox). Migrates direct-flight search to FlightAware; multi-stop
and where-can-I-go remain via embedded SFSafariViewController.

- FlightAwareScheduleClient — scrapes route.rvt + trackpoll JSON for
  real schedules without auth. T+0..2 day window. Tests against
  captured HTML fixtures.
- BlobRouteClient — pulls the public Vercel blob route catalog
  route-explorer's frontend reads (no auth, no Turnstile).
- DiagnosticLogger + LoggingURLSessionDelegate + DiagnosticsView —
  device-shareable forensic trace. Boot header captures device, OS,
  locale, UA; share-sheet export of session logs.
- TurnstileDebugView — live WKWebView gate inspector. Used to prove
  the PAT-entitlement gap on a real device.
- RouteExplorerBrowserView — SFSafariViewController wrapper. Real
  Safari clears Turnstile naturally; the in-app browser opens at
  pre-filled search URLs. Surfaced from Search ("Open in
  route-explorer") and Settings → Tools.
- RouteExplorerTokenStore + RouteExplorerSetupView — bookmarklet
  capture flow (token round-tripped via flights://routeexplorer-token
  URL scheme). Kept dormant for future use.

backend/ — Docker proxy attempts (Playwright, patchright, Camoufox).
All fail on Linux because Cloudflare auto-denies before the Turnstile
widget renders. Documented; kept as scaffolding for a future paid-
solver integration.

scripts/probe_flightaware.py — reference algorithm for the FA path.
scripts/probe_nodriver.py — local-Mac sanity check confirming the
gate clears with real macOS Chrome (proves the blocker is
fingerprint-level, not network-level).
2026-06-06 01:09:59 -05:00

135 lines
5.0 KiB
Swift

import XCTest
import SwiftData
@testable import Flights
/// Unit tests for `AirframeHistoryStore`.
///
/// We exercise the store against an in-memory `ModelContainer` seeded
/// with `LoggedFlight` rows that vary by tail number, route, and date.
/// All assertions reference the documented `AirframeStats` contract.
@MainActor
final class AirframeHistoryStoreTests: XCTestCase {
private var container: ModelContainer!
private var context: ModelContext!
private var store: AirframeHistoryStore!
override func setUpWithError() throws {
try super.setUpWithError()
let schema = Schema([LoggedFlight.self])
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
container = try ModelContainer(for: schema, configurations: config)
context = ModelContext(container)
store = AirframeHistoryStore()
}
override func tearDownWithError() throws {
store = nil
context = nil
container = nil
try super.tearDownWithError()
}
// MARK: - Helpers
private static let epoch = Date(timeIntervalSince1970: 1_700_000_000)
private func date(_ dayOffset: Int) -> Date {
Self.epoch.addingTimeInterval(TimeInterval(dayOffset) * 86_400)
}
@discardableResult
private func insert(
registration: String?,
origin: String,
dest: String,
flightDate: Date
) -> LoggedFlight {
let flight = LoggedFlight(
flightDate: flightDate,
departureIATA: origin,
arrivalIATA: dest,
registration: registration
)
context.insert(flight)
return flight
}
// MARK: - Tests
/// Empty store empty stats sentinel.
func test_stats_emptyContext_returnsEmpty() {
let stats = store.stats(forTail: "N281WN", context: context)
XCTAssertEqual(stats.totalFlights, 0)
XCTAssertTrue(stats.routes.isEmpty)
XCTAssertNil(stats.firstSeen)
XCTAssertNil(stats.lastSeen)
XCTAssertNil(stats.mostCommonRoute)
}
/// 3 flights on the same tail across 2 distinct routes verify the
/// aggregate counts and the "DALHOU (2 of 3)" most-common-route
/// formatting.
func test_stats_threeFlightsTwoRoutes_aggregatesCorrectly() {
insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(0))
insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(5))
insert(registration: "N281WN", origin: "DAL", dest: "LAS", flightDate: date(10))
// Other-tail noise must not be counted.
insert(registration: "N999AA", origin: "DAL", dest: "HOU", flightDate: date(2))
let stats = store.stats(forTail: "N281WN", context: context)
XCTAssertEqual(stats.totalFlights, 3)
XCTAssertEqual(Set(stats.routes), Set(["DAL→HOU", "DAL→LAS"]))
XCTAssertEqual(stats.routes.count, 2)
XCTAssertEqual(stats.firstSeen, date(0))
XCTAssertEqual(stats.lastSeen, date(10))
XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (2 of 3)")
}
/// Lookup tail must be normalized to uppercase passing "n281wn"
/// matches a stored "N281WN".
func test_stats_lookupIsCaseInsensitive() {
insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(0))
let stats = store.stats(forTail: "n281wn", context: context)
XCTAssertEqual(stats.totalFlights, 1)
XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (1 of 1)")
}
/// The store should still report stats for a single-flight tail. The
/// History UI hides the section in that case, but the underlying
/// store contract returns the real count.
func test_stats_singleFlight_returnsTotalOne() {
insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(0))
let stats = store.stats(forTail: "N281WN", context: context)
XCTAssertEqual(stats.totalFlights, 1)
XCTAssertEqual(stats.routes, ["DAL→HOU"])
XCTAssertEqual(stats.firstSeen, date(0))
XCTAssertEqual(stats.lastSeen, date(0))
XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (1 of 1)")
}
/// Mixed-case stored registration: a record persisted with lowercase
/// "n281wn" must still be discoverable when callers ask for
/// "N281WN". Today the fast-path #Predicate misses (it compares
/// exact bytes against the uppercased query) and the fallback
/// table-scan recovers it. After Phase 3 fixes registration
/// normalisation at write-time (or switches to a case-insensitive
/// predicate), the fast path will hit but this test should still
/// pass either way.
func test_stats_lowercaseStoredRegistration_isFoundViaFallback() {
insert(registration: "n281wn", origin: "DAL", dest: "HOU", flightDate: date(0))
let stats = store.stats(forTail: "N281WN", context: context)
XCTAssertEqual(stats.totalFlights, 1)
XCTAssertEqual(stats.routes, ["DAL→HOU"])
XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (1 of 1)")
}
}