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 "DAL→HOU (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)") } }