diff --git a/.gitignore b/.gitignore index f47250e1f2..e77011eec4 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ docs/.astro/ # Swift Package Manager metadata (leave sources tracked) # Packages/ # Package.resolved + +# fanout round cache (local tooling) +.fanout-cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e76f7a93..3c473d98b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.37.3 — Unreleased +### Fixed +- Cost usage: "Today" now reflects the current local day and shows $0.00 · 0 tokens when there is no usage today, instead of the latest historical bucket — across the token-cost menu card, inline dashboard, and the OpenAI/Claude Admin and Poe summaries. Thanks @LeoLin990405! + ## 0.37.2 — 2026-06-22 ### Added diff --git a/Sources/CodexBar/InlineUsageDashboardContent.swift b/Sources/CodexBar/InlineUsageDashboardContent.swift index 762007bdc0..cc546c5fe4 100644 --- a/Sources/CodexBar/InlineUsageDashboardContent.swift +++ b/Sources/CodexBar/InlineUsageDashboardContent.swift @@ -89,7 +89,7 @@ extension UsageMenuCardView.Model { } static func openAIAPIUsageNotes(_ usage: OpenAIAPIUsageSnapshot) -> [String] { - let today = usage.latestDay + let today = usage.currentDay() let seven = usage.last7Days let thirty = usage.last30Days let historyLabel = usage.historyWindowLabel @@ -114,7 +114,7 @@ extension UsageMenuCardView.Model { } static func poeUsageNotes(_ usage: PoeUsageHistorySnapshot) -> [String] { - let today = usage.latestDay + let today = usage.currentDay() let week = usage.last7Days let month = usage.last30Days let todayUSD = today.costUSD.map { " · \(UsageFormatter.usdString($0))" } ?? "" @@ -230,7 +230,7 @@ extension UsageMenuCardView.Model { } static func poeInlineDashboard(_ usage: PoeUsageHistorySnapshot) -> InlineUsageDashboardModel { - let today = usage.latestDay + let today = usage.currentDay() let week = usage.last7Days let month = usage.last30Days let points = usage.daily.suffix(30).map { @@ -332,13 +332,18 @@ extension UsageMenuCardView.Model { details.append(L("cost_estimate_hint")) } let providerName = ProviderDefaults.metadata[provider]?.displayName ?? provider.rawValue + let usesLatestLabel = provider == .bedrock || provider == .mistral return InlineUsageDashboardModel( accessibilityLabel: "\(providerName) \(periodLabel) cost trend", valueStyle: Self.costValueStyle(currencyCode: snapshot.currencyCode), kpis: [ .init( - title: provider == .bedrock || provider == .mistral ? L("Latest") : L("Today"), - value: latest?.costUSD.map { Self.costString($0, currencyCode: snapshot.currencyCode) } ?? "—", + title: usesLatestLabel ? L("Latest") : L("Today"), + // "Today" must show the current local day (snapshot.sessionCostUSD, + // already zeroed when there's no usage today); only "Latest" + // providers keep the newest historical bucket (#1705). + value: (usesLatestLabel ? latest?.costUSD : snapshot.sessionCostUSD) + .map { Self.costString($0, currencyCode: snapshot.currencyCode) } ?? "—", emphasis: true), .init( title: historyTitle, @@ -378,7 +383,7 @@ extension UsageMenuCardView.Model { fileprivate static func claudeAdminAPIInlineDashboard(_ usage: ClaudeAdminAPIUsageSnapshot) -> InlineUsageDashboardModel { - let today = usage.latestDay + let today = usage.currentDay() let last7 = usage.last7Days let last30 = usage.last30Days let points = usage.daily.suffix(30).map { diff --git a/Sources/CodexBar/MenuDescriptor.swift b/Sources/CodexBar/MenuDescriptor.swift index 75cc63d059..de5d2626e7 100644 --- a/Sources/CodexBar/MenuDescriptor.swift +++ b/Sources/CodexBar/MenuDescriptor.swift @@ -293,7 +293,7 @@ struct MenuDescriptor { entries: inout [Entry], usage: OpenAIAPIUsageSnapshot) { - let today = usage.latestDay + let today = usage.currentDay() let last7 = usage.last7Days let last30 = usage.last30Days let historyLabel = usage.historyWindowLabel @@ -319,7 +319,7 @@ struct MenuDescriptor { entries: inout [Entry], usage: ClaudeAdminAPIUsageSnapshot) { - let today = usage.latestDay + let today = usage.currentDay() let last7 = usage.last7Days let last30 = usage.last30Days @@ -393,7 +393,7 @@ struct MenuDescriptor { entries: inout [Entry], usage: PoeUsageHistorySnapshot) { - let today = usage.latestDay + let today = usage.currentDay() let week = usage.last7Days let month = usage.last30Days let todayCostSuffix = today.costUSD.map { " · \(UsageFormatter.usdString($0))" } ?? "" diff --git a/Sources/CodexBarCore/CostUsageFetcher.swift b/Sources/CodexBarCore/CostUsageFetcher.swift index e019ff608e..0a5c37fae7 100644 --- a/Sources/CodexBarCore/CostUsageFetcher.swift +++ b/Sources/CodexBarCore/CostUsageFetcher.swift @@ -116,7 +116,12 @@ public struct CostUsageFetcher: Sendable { environment: environment, since: since, until: until) - return Self.tokenSnapshot(from: daily, now: now, historyDays: clampedHistoryDays) + // AWS Cost Explorer lags, so Bedrock shows the latest billing day, not "Today". + return Self.tokenSnapshot( + from: daily, + now: now, + historyDays: clampedHistoryDays, + sessionDay: .latest) } var options = overrideScannerOptions ?? CostUsageScanner.Options() @@ -273,26 +278,45 @@ public struct CostUsageFetcher: Sendable { environment: environment) } + /// How the session ("first KPI") row is selected. + enum SessionDaySelection { + /// Current local day; zero when there is no usage today (default; "Today" labels). + case today + /// Newest historical bucket; for lagged sources labeled "Latest billing day" + /// (e.g. AWS Cost Explorer), where today's row legitimately does not exist yet. + case latest + } + static func tokenSnapshot( from daily: CostUsageDailyReport, now: Date, - historyDays: Int = 30) -> CostUsageTokenSnapshot + historyDays: Int = 30, + sessionDay: SessionDaySelection = .today) -> CostUsageTokenSnapshot { - // Pick the most recent day; break ties by cost/tokens to keep a stable "session" row. - let currentDay = daily.data.compactMap { entry -> (entry: CostUsageDailyReport.Entry, date: Date)? in - guard let date = CostUsageDateParser.parse(entry.date) else { return nil } - return (entry, date) + let sessionTokens: Int? + let sessionCostUSD: Double? + switch sessionDay { + case .latest: + // "Latest billing day": newest historical bucket, never zeroed. + let latest = CostUsageTokenSnapshot.latestEntry(in: daily.data) + sessionTokens = latest?.totalTokens + sessionCostUSD = latest?.costUSD + case .today: + // "Today" must reflect the current local day only, never the latest bucket: + // - a row for today => that row's values + // - history exists but no row for today => known zero ($0.00 · 0 tokens) + // - no history at all => nil (unknown) + if let currentDay = CostUsageTokenSnapshot.entry(in: daily.data, forLocalDayContaining: now) { + sessionTokens = currentDay.totalTokens + sessionCostUSD = currentDay.costUSD + } else if daily.data.isEmpty { + sessionTokens = nil + sessionCostUSD = nil + } else { + sessionTokens = 0 + sessionCostUSD = 0 + } } - .max { lhs, rhs in - if lhs.date != rhs.date { return lhs.date < rhs.date } - let lCost = lhs.entry.costUSD ?? -1 - let rCost = rhs.entry.costUSD ?? -1 - if lCost != rCost { return lCost < rCost } - let lTokens = lhs.entry.totalTokens ?? -1 - let rTokens = rhs.entry.totalTokens ?? -1 - if lTokens != rTokens { return lTokens < rTokens } - return lhs.entry.date < rhs.entry.date - }?.entry // Prefer summary totals when present; fall back to summing daily entries. let totalFromSummary = daily.summary?.totalCostUSD let totalFromEntries = daily.data.compactMap(\.costUSD).reduce(0, +) @@ -302,8 +326,8 @@ public struct CostUsageFetcher: Sendable { let last30DaysTokens = totalTokensFromSummary ?? (totalTokensFromEntries > 0 ? totalTokensFromEntries : nil) return CostUsageTokenSnapshot( - sessionTokens: currentDay?.totalTokens, - sessionCostUSD: currentDay?.costUSD, + sessionTokens: sessionTokens, + sessionCostUSD: sessionCostUSD, last30DaysTokens: last30DaysTokens, last30DaysCostUSD: last30DaysCostUSD, historyDays: historyDays, diff --git a/Sources/CodexBarCore/CostUsageModels.swift b/Sources/CodexBarCore/CostUsageModels.swift index 30dcbb7012..1686a4c0b2 100644 --- a/Sources/CodexBarCore/CostUsageModels.swift +++ b/Sources/CodexBarCore/CostUsageModels.swift @@ -42,6 +42,50 @@ public struct CostUsageTokenSnapshot: Sendable, Equatable { } } +extension CostUsageTokenSnapshot { + /// Stable key (`yyyy-MM-dd`) identifying the local calendar day that `date` falls in. + public static func localDayKey(from date: Date, calendar: Calendar = .current) -> String { + let components = calendar.dateComponents([.year, .month, .day], from: date) + return String(format: "%04d-%02d-%02d", components.year ?? 0, components.month ?? 0, components.day ?? 0) + } + + /// Entry whose parsed date maps to the same local-day key as `date`; `nil` if none. + public static func entry( + in entries: [CostUsageDailyReport.Entry], + forLocalDayContaining date: Date, + calendar: Calendar = .current) -> CostUsageDailyReport.Entry? + { + let targetKey = self.localDayKey(from: date, calendar: calendar) + return entries.first { entry in + guard let entryDate = CostUsageDateParser.parse(entry.date) else { return false } + return self.localDayKey(from: entryDate, calendar: calendar) == targetKey + } + } + + /// Newest-day entry, breaking ties by cost then tokens then date string for a stable pick. + public static func latestEntry(in entries: [CostUsageDailyReport.Entry]) -> CostUsageDailyReport.Entry? { + entries.compactMap { entry -> (entry: CostUsageDailyReport.Entry, date: Date)? in + guard let date = CostUsageDateParser.parse(entry.date) else { return nil } + return (entry, date) + } + .max { lhs, rhs in + if lhs.date != rhs.date { return lhs.date < rhs.date } + let lCost = lhs.entry.costUSD ?? -1 + let rCost = rhs.entry.costUSD ?? -1 + if lCost != rCost { return lCost < rCost } + let lTokens = lhs.entry.totalTokens ?? -1 + let rTokens = rhs.entry.totalTokens ?? -1 + if lTokens != rTokens { return lTokens < rTokens } + return lhs.entry.date < rhs.entry.date + }?.entry + } + + /// Entry for the local day containing `updatedAt`; `nil` when there is no usage today. + public func currentDayEntry(calendar: Calendar = .current) -> CostUsageDailyReport.Entry? { + Self.entry(in: self.daily, forLocalDayContaining: self.updatedAt, calendar: calendar) + } +} + public struct CostUsageDailyReport: Sendable, Decodable { public struct ModelBreakdown: Sendable, Decodable, Equatable { public let modelName: String diff --git a/Sources/CodexBarCore/Providers/Claude/ClaudeAdminAPIUsageSnapshot.swift b/Sources/CodexBarCore/Providers/Claude/ClaudeAdminAPIUsageSnapshot.swift index f9b82ab0c8..dfcae6e468 100644 --- a/Sources/CodexBarCore/Providers/Claude/ClaudeAdminAPIUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/Claude/ClaudeAdminAPIUsageSnapshot.swift @@ -133,6 +133,34 @@ public struct ClaudeAdminAPIUsageSnapshot: Codable, Equatable, Sendable { self.summary(days: 1) } + /// Summary for the current local day (derived from `updatedAt`), or a zero + /// summary when there is no bucket for today — so a "Today" label never shows + /// a stale historical bucket (#1705). + public func currentDay(calendar: Calendar = .current) -> Summary { + // Match on the bucket's absolute startTime so the local day is resolved + // correctly even though `day` strings are UTC-based. + let key = CostUsageTokenSnapshot.localDayKey(from: self.updatedAt, calendar: calendar) + let match = self.daily.first { bucket in + CostUsageTokenSnapshot.localDayKey(from: bucket.startTime, calendar: calendar) == key + } + guard let match else { + return Summary( + costUSD: 0, + inputTokens: 0, + cacheCreationInputTokens: 0, + cacheReadInputTokens: 0, + outputTokens: 0, + totalTokens: 0) + } + return Summary( + costUSD: match.costUSD, + inputTokens: match.inputTokens, + cacheCreationInputTokens: match.cacheCreationInputTokens, + cacheReadInputTokens: match.cacheReadInputTokens, + outputTokens: match.outputTokens, + totalTokens: match.totalTokens) + } + public func summary(days: Int) -> Summary { let selected = self.daily.suffix(max(1, days)) return Summary( diff --git a/Sources/CodexBarCore/Providers/OpenAI/OpenAIAPIUsageSnapshot.swift b/Sources/CodexBarCore/Providers/OpenAI/OpenAIAPIUsageSnapshot.swift index 9f741ea994..e6f5f453d0 100644 --- a/Sources/CodexBarCore/Providers/OpenAI/OpenAIAPIUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/OpenAI/OpenAIAPIUsageSnapshot.swift @@ -145,6 +145,34 @@ public struct OpenAIAPIUsageSnapshot: Codable, Equatable, Sendable { self.summary(days: 1) } + /// Summary for the current local day (derived from `updatedAt`), or a zero + /// summary when there is no bucket for today — so a "Today" label never shows + /// a stale historical bucket (#1705). + public func currentDay(calendar: Calendar = .current) -> Summary { + // Match on the bucket's absolute startTime so the local day is resolved + // correctly even though `day` strings are UTC-based. + let key = CostUsageTokenSnapshot.localDayKey(from: self.updatedAt, calendar: calendar) + let match = self.daily.first { bucket in + CostUsageTokenSnapshot.localDayKey(from: bucket.startTime, calendar: calendar) == key + } + guard let match else { + return Summary( + costUSD: 0, + requests: 0, + inputTokens: 0, + cachedInputTokens: 0, + outputTokens: 0, + totalTokens: 0) + } + return Summary( + costUSD: match.costUSD, + requests: match.requests, + inputTokens: match.inputTokens, + cachedInputTokens: match.cachedInputTokens, + outputTokens: match.outputTokens, + totalTokens: match.totalTokens) + } + public func summary(days: Int) -> Summary { let selected = self.daily.suffix(max(1, days)) return Summary( @@ -238,12 +266,14 @@ public struct OpenAIAPIUsageSnapshot: Codable, Equatable, Sendable { modelsUsed: modelsUsed.isEmpty ? nil : modelsUsed, modelBreakdowns: modelBreakdowns.isEmpty ? nil : modelBreakdowns) } - let latest = self.latestDay + // sessionTokens/sessionCostUSD feed "Today" labels, so use the current local + // day (zero when no usage today), never the latest historical bucket (#1705). + let today = self.currentDay() let total = self.last30Days return CostUsageTokenSnapshot( - sessionTokens: latest.totalTokens, - sessionCostUSD: latest.costUSD, - sessionRequests: latest.requests, + sessionTokens: today.totalTokens, + sessionCostUSD: today.costUSD, + sessionRequests: today.requests, last30DaysTokens: total.totalTokens, last30DaysCostUSD: total.costUSD, last30DaysRequests: total.requests, diff --git a/Sources/CodexBarCore/Providers/Poe/PoeUsageHistorySnapshot.swift b/Sources/CodexBarCore/Providers/Poe/PoeUsageHistorySnapshot.swift index 63cceb5e58..fc77365ca4 100644 --- a/Sources/CodexBarCore/Providers/Poe/PoeUsageHistorySnapshot.swift +++ b/Sources/CodexBarCore/Providers/Poe/PoeUsageHistorySnapshot.swift @@ -71,6 +71,20 @@ public struct PoeUsageHistorySnapshot: Codable, Equatable, Sendable { self.summary(days: 1) } + /// Summary for the current local day (derived from `updatedAt`), or a zero + /// summary when there is no bucket for today — so a "Today" label never shows + /// a stale historical bucket (#1705). + public func currentDay(calendar: Calendar = .current) -> Summary { + // Poe buckets carry only a `yyyy-MM-dd` day (no timestamp), so compare the + // day string directly against the local-day key. + let key = CostUsageTokenSnapshot.localDayKey(from: self.updatedAt, calendar: calendar) + let match = self.daily.first { $0.day == key } + guard let match else { + return Summary(points: 0, requests: 0, costUSD: nil) + } + return Summary(points: match.points, requests: match.requests, costUSD: match.costUSD) + } + public var last7Days: Summary { self.summary(days: 7) } diff --git a/Tests/CodexBarTests/CostUsageDecodingTests.swift b/Tests/CodexBarTests/CostUsageDecodingTests.swift index 33eb9df2c1..bb342c379a 100644 --- a/Tests/CodexBarTests/CostUsageDecodingTests.swift +++ b/Tests/CodexBarTests/CostUsageDecodingTests.swift @@ -434,7 +434,10 @@ struct CostUsageDecodingTests { """ let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8)) - let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: Date()) + // "Today" is the current local day (2026-05-13 here); the impossible 2026-06-31 + // row must never be parsed/selected. Fixed `now` keeps this deterministic. + let now = Date(timeIntervalSince1970: 1_778_630_400) // 2026-05-13 + let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: now) #expect(snapshot.sessionTokens == 30) #expect(snapshot.sessionCostUSD == 23.45) diff --git a/Tests/CodexBarTests/CostUsageTodayBucketTests.swift b/Tests/CodexBarTests/CostUsageTodayBucketTests.swift new file mode 100644 index 0000000000..ee85bf16b6 --- /dev/null +++ b/Tests/CodexBarTests/CostUsageTodayBucketTests.swift @@ -0,0 +1,173 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CostUsageTodayBucketTests { + // MARK: - Helpers + + /// Returns a fixed calendar date built from explicit components (UTC). + private static func fixedDate( + _ year: Int, + _ month: Int, + _ day: Int, + calendar: Calendar = .current) -> Date + { + var comps = DateComponents() + comps.year = year + comps.month = month + comps.day = day + comps.hour = 12 + return calendar.date(from: comps)! + } + + /// Shorthand for yyyy-MM-dd string entries use. + private static func dayString(_ year: Int, _ month: Int, _ day: Int) -> String { + String(format: "%04d-%02d-%02d", year, month, day) + } + + /// A one-day-old entry with modest token/cost values. + private static func pastEntry( + year: Int, + month: Int, + day: Int, + tokens: Int = 500, + cost: Double = 0.03) -> CostUsageDailyReport.Entry + { + CostUsageDailyReport.Entry( + date: self.dayString(year, month, day), + inputTokens: tokens / 2, + outputTokens: tokens / 2, + totalTokens: tokens, + requestCount: 3, + costUSD: cost, + modelsUsed: ["test-model"], + modelBreakdowns: nil) + } + + /// A today-ish entry. + private static func todayEntry( + year: Int, month: Int, day: Int) -> CostUsageDailyReport.Entry + { + CostUsageDailyReport.Entry( + date: self.dayString(year, month, day), + inputTokens: 120, + outputTokens: 80, + totalTokens: 200, + requestCount: 1, + costUSD: 0.01, + modelsUsed: ["todays-model"], + modelBreakdowns: nil) + } + + // MARK: - 1. No local-day row → session reports zero, historical totals preserved + + @Test + func `token snapshot with past rows only reports zero today but keeps history`() { + // "now" is June 22, 2026; the daily rows only go up to June 20. + let now = Self.fixedDate(2026, 6, 22) + let past = Self.pastEntry(year: 2026, month: 6, day: 20, tokens: 600, cost: 0.5) + let older = Self.pastEntry(year: 2026, month: 6, day: 19, tokens: 300, cost: 0.25) + let report = CostUsageDailyReport(data: [older, past], summary: nil) + + // Drive the actual fixed path, not a hand-built snapshot. + let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: now) + + // History exists but no row for today → known zero ($0.00 · 0 tokens), + // never the latest historical bucket. + #expect(snapshot.sessionTokens == 0) + #expect(snapshot.sessionCostUSD == 0) + // Historical totals are preserved (0.5 + 0.25 is binary-exact). + #expect(snapshot.last30DaysTokens == 900) + #expect(snapshot.last30DaysCostUSD == 0.75) + #expect(snapshot.daily.count == 2) + } + + @Test + func `empty history reports nil today`() { + let now = Self.fixedDate(2026, 6, 22) + let snapshot = CostUsageFetcher.tokenSnapshot(from: CostUsageDailyReport(data: [], summary: nil), now: now) + #expect(snapshot.sessionTokens == nil) + #expect(snapshot.sessionCostUSD == nil) + } + + // MARK: - 2. Local-day row EXISTS → session populated + + @Test + func `token snapshot with matching local-day row populates session values`() { + let calendar = Calendar(identifier: .gregorian) + let now = Self.fixedDate(2026, 6, 22, calendar: calendar) + let today = Self.todayEntry(year: 2026, month: 6, day: 22) + let yesterday = Self.pastEntry(year: 2026, month: 6, day: 21, tokens: 400, cost: 0.02) + + let snapshot = CostUsageTokenSnapshot( + sessionTokens: 200, // pre-populated by fetcher from today's row + sessionCostUSD: 0.01, + last30DaysTokens: 600, + last30DaysCostUSD: 0.03, + daily: [yesterday, today], + updatedAt: now) + + let todayRow = CostUsageTokenSnapshot.entry( + in: snapshot.daily, + forLocalDayContaining: now, + calendar: calendar) + + #expect(todayRow != nil) + #expect(todayRow?.totalTokens == 200) + #expect(todayRow?.costUSD == 0.01) + + // session fields match the today-row values. + #expect(snapshot.sessionTokens == 200) + #expect(snapshot.sessionCostUSD == 0.01) + } + + // MARK: - 3. latestEntry — always the newest historical row + + @Test + func `latestEntry returns newest historical row regardless of today`() { + let e1 = Self.pastEntry(year: 2026, month: 6, day: 18, tokens: 100, cost: 0.01) + let e2 = Self.pastEntry(year: 2026, month: 6, day: 19, tokens: 200, cost: 0.02) + let e3 = Self.pastEntry(year: 2026, month: 6, day: 20, tokens: 300, cost: 0.03) + let entries = [e1, e2, e3] + + let latest = CostUsageTokenSnapshot.latestEntry(in: entries) + #expect(latest?.date == Self.dayString(2026, 6, 20)) + #expect(latest?.totalTokens == 300) + } + + @Test + func `latestEntry returns nil for empty array`() { + #expect(CostUsageTokenSnapshot.latestEntry(in: []) == nil) + } + + // MARK: - 4. entry(in:forLocalDayContaining:) boundary accuracy + + @Test + func `entry for local day matches exact date and returns nil across day boundary`() { + let calendar = Calendar(identifier: .gregorian) + let e1 = Self.pastEntry(year: 2026, month: 6, day: 20, tokens: 500, cost: 0.03) + let e2 = Self.pastEntry(year: 2026, month: 6, day: 21, tokens: 600, cost: 0.04) + let entries = [e1, e2] + + // Query June 20 → must hit e1. + let r1 = CostUsageTokenSnapshot.entry( + in: entries, + forLocalDayContaining: Self.fixedDate(2026, 6, 20, calendar: calendar), + calendar: calendar) + #expect(r1?.totalTokens == 500) + + // Query June 21 → must hit e2. + let r2 = CostUsageTokenSnapshot.entry( + in: entries, + forLocalDayContaining: Self.fixedDate(2026, 6, 21, calendar: calendar), + calendar: calendar) + #expect(r2?.totalTokens == 600) + + // Query June 22 (no row) → nil. + let r3 = CostUsageTokenSnapshot.entry( + in: entries, + forLocalDayContaining: Self.fixedDate(2026, 6, 22, calendar: calendar), + calendar: calendar) + #expect(r3 == nil) + } +} diff --git a/Tests/CodexBarTests/MenuCardModelTests.swift b/Tests/CodexBarTests/MenuCardModelTests.swift index 18dfc1443a..daf8c8dd78 100644 --- a/Tests/CodexBarTests/MenuCardModelTests.swift +++ b/Tests/CodexBarTests/MenuCardModelTests.swift @@ -62,7 +62,7 @@ struct OverviewMenuCardVisibilityTests { struct ProviderInlineDashboardModelTests { @Test func `claude admin api usage gets inline dashboard`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.claude]) let usage = ClaudeAdminAPIUsageSnapshot( daily: [ @@ -122,7 +122,7 @@ struct ProviderInlineDashboardModelTests { @Test func `openrouter period usage gets inline dashboard`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.openrouter]) let usage = OpenRouterUsageSnapshot( totalCredits: 100, @@ -165,7 +165,7 @@ struct ProviderInlineDashboardModelTests { @Test func `local cost history gets inline dashboard`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.claude]) let daily = [ CostUsageDailyReport.Entry( @@ -235,7 +235,7 @@ struct ProviderInlineDashboardModelTests { @Test func `mistral daily buckets get inline dashboard`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.mistral]) let snapshot = MistralUsageSnapshot( totalCost: 1.5, @@ -292,7 +292,7 @@ struct ProviderInlineDashboardModelTests { @Test func `mistral billing usage can show cost card summary`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.mistral]) let snapshot = MistralUsageSnapshot( totalCost: 1.5, diff --git a/Tests/CodexBarTests/MenuDescriptorOpenAIAPITests.swift b/Tests/CodexBarTests/MenuDescriptorOpenAIAPITests.swift index d45b96429e..574295bc1f 100644 --- a/Tests/CodexBarTests/MenuDescriptorOpenAIAPITests.swift +++ b/Tests/CodexBarTests/MenuDescriptorOpenAIAPITests.swift @@ -22,7 +22,9 @@ struct MenuDescriptorOpenAIAPITests { fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) - let now = Date(timeIntervalSince1970: 1_700_179_200) + // 2023-11-14 12:00 UTC — current local day matches the latest bucket so + // "Today" reflects it (#1705). + let now = Date(timeIntervalSince1970: 1_699_963_200) let usage = OpenAIAPIUsageSnapshot( daily: [ OpenAIAPIUsageSnapshot.DailyBucket( diff --git a/Tests/CodexBarTests/MenuDescriptorPoeTests.swift b/Tests/CodexBarTests/MenuDescriptorPoeTests.swift index 1f5dc5b212..24c02a72be 100644 --- a/Tests/CodexBarTests/MenuDescriptorPoeTests.swift +++ b/Tests/CodexBarTests/MenuDescriptorPoeTests.swift @@ -72,7 +72,9 @@ struct MenuDescriptorPoeTests { browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) - let now = Date(timeIntervalSince1970: 1_717_171_717) + // 2026-05-31 12:00 UTC — current local day matches the latest bucket so + // "Today" reflects it (#1705). + let now = Date(timeIntervalSince1970: 1_780_228_800) let history = PoeUsageHistorySnapshot( entries: [ .init( diff --git a/Tests/CodexBarTests/OpenAIAPIMenuCardModelTests.swift b/Tests/CodexBarTests/OpenAIAPIMenuCardModelTests.swift index b13eabe477..c03cef5243 100644 --- a/Tests/CodexBarTests/OpenAIAPIMenuCardModelTests.swift +++ b/Tests/CodexBarTests/OpenAIAPIMenuCardModelTests.swift @@ -6,7 +6,7 @@ import Testing struct OpenAIAPIMenuCardModelTests { @Test func `admin usage model shows summaries and spend without fake quota bars`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.openai]) let apiUsage = OpenAIAPIUsageSnapshot( daily: [ @@ -71,7 +71,7 @@ struct OpenAIAPIMenuCardModelTests { @Test func `admin usage dashboard ignores stale token snapshot after fallback refresh`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.openai]) let staleTokenSnapshot = CostUsageTokenSnapshot( sessionTokens: 1500, @@ -119,7 +119,7 @@ struct OpenAIAPIMenuCardModelTests { @Test func `admin usage model can show cost card summary`() throws { - let now = Date(timeIntervalSince1970: 1_700_179_200) + let now = Date(timeIntervalSince1970: 1_699_963_200) // 2023-11-14 12:00 UTC, matches latest bucket (#1705) let metadata = try #require(ProviderDefaults.metadata[.openai]) let apiUsage = OpenAIAPIUsageSnapshot( daily: [ diff --git a/Tests/CodexBarTests/OpenAIAPIUsageFetcherTests.swift b/Tests/CodexBarTests/OpenAIAPIUsageFetcherTests.swift index a9cd8f50ee..9b69d92373 100644 --- a/Tests/CodexBarTests/OpenAIAPIUsageFetcherTests.swift +++ b/Tests/CodexBarTests/OpenAIAPIUsageFetcherTests.swift @@ -386,7 +386,9 @@ struct OpenAIAPIUsageFetcherTests { @Test func `maps project scoped admin usage to cost token snapshot`() { - let now = Date(timeIntervalSince1970: 1_700_179_200) + // 2023-11-14 12:00 UTC — the current local day must match the latest + // bucket so "Today" reflects it (#1705). + let now = Date(timeIntervalSince1970: 1_699_963_200) let apiUsage = OpenAIAPIUsageSnapshot( daily: [ OpenAIAPIUsageSnapshot.DailyBucket(