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).
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import XCTest
|
||||
@testable import Flights
|
||||
|
||||
/// Coverage for `DataIntegrityMonitor` — the shared sink that collects
|
||||
/// bundled-JSON decode failures so `RootView` can show a banner instead
|
||||
/// of leaving the user staring at "no data" with no explanation.
|
||||
///
|
||||
/// The monitor is `@MainActor` because it's read by SwiftUI views, so
|
||||
/// every test hop onto the main actor before touching it. Each test also
|
||||
/// calls `clear()` first because the singleton is process-wide and other
|
||||
/// loaders may have reported into it during test bring-up.
|
||||
@MainActor
|
||||
final class DataIntegrityMonitorTests: XCTestCase {
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
DataIntegrityMonitor.shared.clear()
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
DataIntegrityMonitor.shared.clear()
|
||||
try await super.tearDown()
|
||||
}
|
||||
|
||||
func test_reportingOneFailure_setsHasFailuresTrue() {
|
||||
let monitor = DataIntegrityMonitor.shared
|
||||
XCTAssertFalse(monitor.hasFailures, "monitor should start empty after clear()")
|
||||
|
||||
let err = NSError(
|
||||
domain: "TestDomain",
|
||||
code: 1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "bad json"]
|
||||
)
|
||||
monitor.report("bts_bundle.json", error: err)
|
||||
|
||||
XCTAssertTrue(monitor.hasFailures)
|
||||
XCTAssertEqual(monitor.failures.count, 1)
|
||||
XCTAssertTrue(
|
||||
monitor.failures[0].contains("bts_bundle.json"),
|
||||
"failure entry should include the resource basename"
|
||||
)
|
||||
XCTAssertTrue(
|
||||
monitor.failures[0].contains("bad json"),
|
||||
"failure entry should include the localized description"
|
||||
)
|
||||
}
|
||||
|
||||
func test_reportingTwoFailures_accumulates() {
|
||||
let monitor = DataIntegrityMonitor.shared
|
||||
|
||||
monitor.report(
|
||||
"jumpseat_rules.json",
|
||||
error: NSError(domain: "T", code: 1, userInfo: [NSLocalizedDescriptionKey: "missing field"])
|
||||
)
|
||||
monitor.report(
|
||||
"crewbases.json",
|
||||
error: NSError(domain: "T", code: 2, userInfo: [NSLocalizedDescriptionKey: "trailing comma"])
|
||||
)
|
||||
|
||||
XCTAssertEqual(monitor.failures.count, 2)
|
||||
XCTAssertTrue(monitor.hasFailures)
|
||||
XCTAssertTrue(monitor.failures[0].contains("jumpseat_rules.json"))
|
||||
XCTAssertTrue(monitor.failures[1].contains("crewbases.json"))
|
||||
}
|
||||
|
||||
func test_clear_resetsHasFailures() {
|
||||
let monitor = DataIntegrityMonitor.shared
|
||||
|
||||
monitor.report(
|
||||
"partner_matrix.json",
|
||||
error: NSError(domain: "T", code: 1, userInfo: [NSLocalizedDescriptionKey: "broken"])
|
||||
)
|
||||
XCTAssertTrue(monitor.hasFailures, "precondition: monitor has at least one failure")
|
||||
|
||||
monitor.clear()
|
||||
|
||||
XCTAssertFalse(monitor.hasFailures)
|
||||
XCTAssertEqual(monitor.failures.count, 0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user