Files
KisaniCal/KisaniCalTests/AnalyticsStoreTests.swift

95 lines
4.7 KiB
Swift
Raw Normal View History

import XCTest
@testable import KisaniCal
/// Persistence tests: snapshot immutability, report idempotency/dedup,
/// reconciliation selection, and retention pruning. Each test runs against a
/// fresh temp directory. (Verified standalone via swiftc; mirrored here for the
/// Xcode test target.)
final class AnalyticsStoreTests: XCTestCase {
private var tmp: URL!
private var store: AnalyticsStore!
private let now = Date(timeIntervalSince1970: 1_783_500_000) // 2026-07-08
private func cal(_ tz: String = "UTC") -> Calendar {
var c = Calendar(identifier: .gregorian)
c.timeZone = TimeZone(identifier: tz)!
c.locale = Locale(identifier: "en_US_POSIX")
return c
}
override func setUp() {
super.setUp()
tmp = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("analytics_test_\(UUID().uuidString)")
store = AnalyticsStore(root: tmp)
}
override func tearDown() {
try? FileManager.default.removeItem(at: tmp)
super.tearDown()
}
func testPastSnapshotIsFrozen() {
let past = DaySample(dateKey: "2026-06-01", didWorkout: true, scheduled: true, sessions: 1, sets: 10, volume: 500)
XCTAssertTrue(store.saveSnapshot(past, isPast: true))
let edited = DaySample(dateKey: "2026-06-01", didWorkout: true, scheduled: true, sessions: 9, sets: 99, volume: 9999)
XCTAssertFalse(store.saveSnapshot(edited, isPast: true), "past snapshot must not be overwritten")
XCTAssertEqual(store.snapshot(dateKey: "2026-06-01")?.sets, 10)
}
func testTodaySnapshotUpdatable() {
_ = store.saveSnapshot(DaySample(dateKey: "2026-07-08", didWorkout: false, scheduled: true), isPast: false)
XCTAssertTrue(store.saveSnapshot(DaySample(dateKey: "2026-07-08", didWorkout: true, scheduled: true, sessions: 1, sets: 20, volume: 1000), isPast: false))
XCTAssertEqual(store.snapshot(dateKey: "2026-07-08")?.sets, 20)
}
func testWeeklyReportIdempotentAndImmutable() {
let wr = AnalyticsEngine.weeklyReport(
weekStartKey: "2026-06-29",
days: [DaySample(dateKey: "2026-06-29", didWorkout: true, scheduled: true, sessions: 1, sets: 20, volume: 1000)],
previousWeekDays: [DaySample(dateKey: "2026-06-22", didWorkout: true, scheduled: true, sessions: 1, sets: 18, volume: 800)],
generatedAt: now)
XCTAssertTrue(store.saveWeeklyReport(wr))
XCTAssertFalse(store.saveWeeklyReport(wr), "re-saving must be a no-op (dedup)")
XCTAssertEqual(store.weeklyReport(weekStartKey: "2026-06-29")?.progression, .improving)
}
func testMonthlyReportDedup() {
let mr = AnalyticsEngine.monthlyReport(
monthKey: "2026-06",
days: [DaySample(dateKey: "2026-06-01", didWorkout: true, scheduled: true, sessions: 1, sets: 20, volume: 1000)],
previousMonthDays: [DaySample(dateKey: "2026-05-01", didWorkout: true, scheduled: true, sessions: 1, sets: 18, volume: 800)],
generatedAt: now)
XCTAssertTrue(store.saveMonthlyReport(mr))
XCTAssertFalse(store.saveMonthlyReport(mr))
XCTAssertNotNil(store.monthlyReport(monthKey: "2026-06"))
}
func testReconciliationSelection() {
let c = cal()
let weekKeys = AnalyticsEngine.completedWeekStartKeys(now: now, months: 12, calendar: c)
XCTAssertTrue((50...53).contains(weekKeys.count))
let currentWeekKey = AnalyticsEngine.dateKey(for: AnalyticsEngine.weekInterval(containing: now, calendar: c)!.start, calendar: c)
XCTAssertFalse(weekKeys.contains(currentWeekKey), "current (incomplete) week excluded")
let monthKeys = AnalyticsEngine.completedMonthKeys(now: now, months: 12, calendar: c)
XCTAssertEqual(monthKeys.count, 12)
XCTAssertFalse(monthKeys.contains("2026-07"), "current month excluded")
// A saved report is not re-selected (idempotent reconciliation).
let wr = AnalyticsEngine.weeklyReport(weekStartKey: weekKeys.last!, days: [], previousWeekDays: [], generatedAt: now)
_ = store.saveWeeklyReport(wr)
XCTAssertFalse(AnalyticsEngine.missingKeys(weekKeys, existing: store.existingWeeklyKeys()).contains(weekKeys.last!))
}
func testRetentionPrune() {
let c = cal()
_ = store.saveSnapshot(DaySample(dateKey: "2025-01-01", didWorkout: true), isPast: true) // >12mo old
_ = store.saveSnapshot(DaySample(dateKey: "2026-07-08", didWorkout: true), isPast: false)
let removed = store.prune(now: now, months: 12, calendar: c)
XCTAssertGreaterThanOrEqual(removed, 1)
XCTAssertNil(store.snapshot(dateKey: "2025-01-01"), "expired snapshot pruned")
XCTAssertNotNil(store.snapshot(dateKey: "2026-07-08"), "recent snapshot survives")
}
}