import CodexBarCore import Foundation import Testing @testable import CodexBar struct UsageStorePlanUtilizationResetCoalescingTests { @Test func `near canonical codex windows merge into canonical history series`() throws { let base = Date(timeIntervalSince1970: 1_700_000_000) let existing = [ planSeries(name: .session, windowMinutes: 300, entries: [ planEntry(at: base, usedPercent: 20), ]), planSeries(name: .weekly, windowMinutes: 10080, entries: [ planEntry(at: base, usedPercent: 40), ]), ] let incoming = [ planSeries(name: .session, windowMinutes: 299, entries: [ planEntry(at: base.addingTimeInterval(3600), usedPercent: 30), ]), planSeries(name: .weekly, windowMinutes: 10079, entries: [ planEntry(at: base.addingTimeInterval(3600), usedPercent: 50), ]), ] let updated = try #require( UsageStore._updatedPlanUtilizationHistoriesForTesting( existingHistories: existing, samples: incoming)) #expect(updated.map { "\($0.name.rawValue):\($0.windowMinutes)" } == ["session:300", "weekly:10080"]) #expect(findSeries(updated, name: .session, windowMinutes: 300)?.entries.map(\.usedPercent) == [20, 30]) #expect(findSeries(updated, name: .weekly, windowMinutes: 10080)?.entries.map(\.usedPercent) == [40, 50]) } @Test func `same hour entry backfills missing reset metadata`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 17, hour: 9))) let existing = planEntry( at: hourStart.addingTimeInterval(10 * 60), usedPercent: 20) let incoming = planEntry( at: hourStart.addingTimeInterval(45 * 60), usedPercent: 30, resetsAt: hourStart.addingTimeInterval(30 * 60)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: [existing], entry: incoming)) #expect(updated.count == 1) #expect(updated[0].capturedAt == incoming.capturedAt) #expect(updated[0].usedPercent == 30) #expect(updated[0].resetsAt == incoming.resetsAt) } @Test func `same hour later higher usage without reset metadata keeps promoted reset boundary`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 17, hour: 9))) let first = planEntry( at: hourStart.addingTimeInterval(10 * 60), usedPercent: 40) let second = planEntry( at: hourStart.addingTimeInterval(25 * 60), usedPercent: 8, resetsAt: hourStart.addingTimeInterval(30 * 60)) let third = planEntry( at: hourStart.addingTimeInterval(50 * 60), usedPercent: 22) let initial = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: [], entry: first)) let promoted = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: initial, entry: second)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: promoted, entry: third)) #expect(updated.count == 1) #expect(updated[0].capturedAt == third.capturedAt) #expect(updated[0].usedPercent == third.usedPercent) #expect(updated[0].resetsAt == second.resetsAt) } @Test func `same hour zero usage with drifting reset coalesces to latest entry`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 20, hour: 0))) let existing = planEntry( at: hourStart.addingTimeInterval(14 * 60), usedPercent: 0, resetsAt: hourStart.addingTimeInterval(5 * 60 * 60 + 14 * 60 + 2)) let incoming = planEntry( at: hourStart.addingTimeInterval(23 * 60), usedPercent: 0, resetsAt: hourStart.addingTimeInterval(5 * 60 * 60 + 14 * 60 + 3)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: [existing], entry: incoming)) #expect(updated.count == 1) #expect(updated[0] == incoming) } @Test func `same hour reset times within two minutes still keep single hourly peak`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 20, hour: 0))) let existing = planEntry( at: hourStart.addingTimeInterval(21 * 60), usedPercent: 10, resetsAt: hourStart.addingTimeInterval(3 * 60 * 60)) let incoming = planEntry( at: hourStart.addingTimeInterval(55 * 60), usedPercent: 10, resetsAt: hourStart.addingTimeInterval(3 * 60 * 60 + 1)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: [existing], entry: incoming)) #expect(updated.count == 1) #expect(updated[0].capturedAt == incoming.capturedAt) #expect(updated[0].usedPercent == 10) #expect(updated[0].resetsAt == incoming.resetsAt) } @Test func `same hour usage drop without meaningful reset still keeps single hourly peak`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 20, hour: 0))) let existing = planEntry( at: hourStart.addingTimeInterval(15 * 60), usedPercent: 40, resetsAt: hourStart.addingTimeInterval(3 * 60 * 60)) let incoming = planEntry( at: hourStart.addingTimeInterval(45 * 60), usedPercent: 5, resetsAt: hourStart.addingTimeInterval(3 * 60 * 60 + 30)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: [existing], entry: incoming)) #expect(updated.count == 1) #expect(updated[0].capturedAt == existing.capturedAt) #expect(updated[0].usedPercent == existing.usedPercent) #expect(updated[0].resetsAt == incoming.resetsAt) } @Test func `same hour reset keeps peak before reset and latest peak after reset`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 20, hour: 0))) let initial = [ planEntry( at: hourStart.addingTimeInterval(5 * 60), usedPercent: 40, resetsAt: hourStart.addingTimeInterval(3 * 60 * 60)), planEntry( at: hourStart.addingTimeInterval(20 * 60), usedPercent: 12, resetsAt: hourStart.addingTimeInterval(8 * 60 * 60)), ] let incoming = planEntry( at: hourStart.addingTimeInterval(45 * 60), usedPercent: 18, resetsAt: hourStart.addingTimeInterval(8 * 60 * 60)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: initial, entry: incoming)) #expect(updated.count == 2) #expect(updated[0].usedPercent == 40) #expect(updated[1].usedPercent == 18) #expect(updated[1].resetsAt == incoming.resetsAt) } @Test func `newer reset within hour replaces earlier post reset record`() throws { let calendar = Calendar(identifier: .gregorian) let hourStart = try #require(calendar.date(from: DateComponents( timeZone: TimeZone(secondsFromGMT: 0), year: 2026, month: 3, day: 20, hour: 0))) let initial = [ planEntry( at: hourStart.addingTimeInterval(5 * 60), usedPercent: 40, resetsAt: hourStart.addingTimeInterval(3 * 60 * 60)), planEntry( at: hourStart.addingTimeInterval(20 * 60), usedPercent: 12, resetsAt: hourStart.addingTimeInterval(8 * 60 * 60)), ] let incoming = planEntry( at: hourStart.addingTimeInterval(50 * 60), usedPercent: 3, resetsAt: hourStart.addingTimeInterval(10 * 60 * 60)) let updated = try #require( UsageStore._updatedPlanUtilizationEntriesForTesting( existingEntries: initial, entry: incoming)) #expect(updated.count == 2) #expect(updated[0].usedPercent == 40) #expect(updated[1] == incoming) } @Test func `merged histories keep series separated by stable name`() throws { let existing = [ planSeries(name: .session, windowMinutes: 300, entries: [ planEntry(at: Date(timeIntervalSince1970: 1_700_000_000), usedPercent: 20), ]), ] let incoming = [ planSeries(name: .weekly, windowMinutes: 10080, entries: [ planEntry(at: Date(timeIntervalSince1970: 1_700_000_000), usedPercent: 40), ]), ] let updated = try #require( UsageStore._updatedPlanUtilizationHistoriesForTesting( existingHistories: existing, samples: incoming)) #expect(findSeries(updated, name: .session, windowMinutes: 300) != nil) #expect(findSeries(updated, name: .weekly, windowMinutes: 10080) != nil) } }