Some checks failed
CI / build-and-test (push) Has been cancelled
Phase 3 core (verifiable): WorkoutAnalyticsAdapter (app data → DaySample), AnalyticsCoordinator (idempotent backfill of missing weekly/monthly reports + retention prune, injectable providers), AnalyticsDeepLink (wenza://analytics report routing). Fixed a reconcile bug caught by tests: a retention-boundary week was generated then immediately pruned, regenerating every run — aligned completedWeekStartKeys to the prune cutoff → idempotent. Tests: AnalyticsAdapterTests + AnalyticsCoordinatorTests. Verified standalone via swiftc (~90 analytics assertions total across engine/store/adapter/coordinator). Remaining (app wiring, notifications, UI) requires an Xcode build — logged in ISSUES.md as device-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
54 lines
2.4 KiB
Swift
54 lines
2.4 KiB
Swift
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
|
|
}
|
|
}
|