Files
KisaniCal/KisaniCalTests/AnalyticsCoordinatorTests.swift
kutesir 065066c8f7
Some checks failed
CI / build-and-test (push) Has been cancelled
analytics(integration): adapter + reconcile coordinator + deep links (KC-51 ph3)
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>
2026-07-07 22:53:20 +03:00

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
}
}