import XCTest @testable import KisaniCal /// Deep-link routing + reconcile idempotency (no duplicate reports across runs). final class AnalyticsCoordinatorTests: XCTestCase { private func cal() -> Calendar { var c = Calendar(identifier: .gregorian) c.timeZone = TimeZone(identifier: "UTC")! c.locale = Locale(identifier: "en_US_POSIX") return c } private let now = Date(timeIntervalSince1970: 1_783_500_000) // 2026-07-08 // MARK: - Deep links func testDeepLinkRoundTrips() { XCTAssertEqual(AnalyticsDeepLink(url: AnalyticsDeepLink.overview.url), .overview) XCTAssertEqual(AnalyticsDeepLink(url: AnalyticsDeepLink.weekly(weekStartKey: "2026-06-29").url), .weekly(weekStartKey: "2026-06-29")) XCTAssertEqual(AnalyticsDeepLink(string: "wenza://analytics/monthly/2026-05"), .monthly(monthKey: "2026-05")) XCTAssertEqual(AnalyticsDeepLink.weekly(weekStartKey: "2026-06-29").url.absoluteString, "wenza://analytics/weekly/2026-06-29") XCTAssertNil(AnalyticsDeepLink(string: "http://x/weekly/2026-06-29")) } // MARK: - Reconcile idempotency func testReconcileGeneratesThenIsIdempotent() { let tmp = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("coord_\(UUID().uuidString)") defer { try? FileManager.default.removeItem(at: tmp) } let store = AnalyticsStore(root: tmp) func sample(_ v: Double) -> [DaySample] { [DaySample(dateKey: "x", didWorkout: true, scheduled: true, sessions: 1, sets: 10, volume: v)] } let coord = AnalyticsCoordinator( store: store, calendar: cal(), retentionMonths: 12, now: { self.now }, weekSamples: { _ in (sample(1000), sample(800)) }, monthSamples: { _ in (sample(4000), sample(3500)) }) let first = coord.reconcile() XCTAssertGreaterThanOrEqual(first.weekliesGenerated.count, 50) XCTAssertEqual(first.monthliesGenerated.count, 12) let second = coord.reconcile() XCTAssertTrue(second.isEmpty, "second run must generate nothing (no duplicate reports)") XCTAssertEqual(store.allWeeklyReports().count, first.weekliesGenerated.count) XCTAssertEqual(store.allMonthlyReports().count, 12) XCTAssertEqual(store.allWeeklyReports().first?.progression, .improving) // 1000 vs 800 } }