196 lines · 8.2 KB · Swift
RawDownload
import Foundationimport Testing@testable import CodexBarCore struct AdminAPIUsageLocalDaySelectionTests {    @Test    func `OpenAI current day includes UTC bucket containing positive timezone morning`() throws {        let calendar = try Self.calendar(timeZoneIdentifier: "Australia/Sydney")        let now = try Self.date(year: 2026, month: 5, day: 18, hour: 8, timeZoneIdentifier: "Australia/Sydney")        let staleUTCStart = try Self.date(year: 2026, month: 5, day: 16, hour: 0, timeZoneIdentifier: "UTC")        let overlappingUTCStart = try Self.date(year: 2026, month: 5, day: 17, hour: 0, timeZoneIdentifier: "UTC")        let usage = OpenAIAPIUsageSnapshot(            daily: [                OpenAIAPIUsageSnapshot.DailyBucket(                    day: "2026-05-16",                    startTime: staleUTCStart,                    endTime: staleUTCStart.addingTimeInterval(86400),                    costUSD: 9,                    requests: 9,                    inputTokens: 900,                    cachedInputTokens: 90,                    outputTokens: 90,                    totalTokens: 990,                    lineItems: [],                    models: []),                OpenAIAPIUsageSnapshot.DailyBucket(                    day: "2026-05-17",                    startTime: overlappingUTCStart,                    endTime: overlappingUTCStart.addingTimeInterval(86400),                    costUSD: 2.5,                    requests: 3,                    inputTokens: 200,                    cachedInputTokens: 20,                    outputTokens: 30,                    totalTokens: 250,                    lineItems: [],                    models: []),            ],            updatedAt: now)         let today = usage.summary(forLocalDayContaining: now, calendar: calendar)         #expect(today.costUSD == 2.5)        #expect(today.requests == 3)        #expect(today.totalTokens == 250)    }     @Test    func `OpenAI current day does not sum adjacent UTC buckets after positive timezone UTC rollover`() throws {        let calendar = try Self.calendar(timeZoneIdentifier: "Australia/Sydney")        let now = try Self.date(year: 2026, month: 5, day: 18, hour: 16, timeZoneIdentifier: "Australia/Sydney")        let previousUTCStart = try Self.date(year: 2026, month: 5, day: 17, hour: 0, timeZoneIdentifier: "UTC")        let currentUTCStart = try Self.date(year: 2026, month: 5, day: 18, hour: 0, timeZoneIdentifier: "UTC")        let usage = OpenAIAPIUsageSnapshot(            daily: [                OpenAIAPIUsageSnapshot.DailyBucket(                    day: "2026-05-17",                    startTime: previousUTCStart,                    endTime: previousUTCStart.addingTimeInterval(86400),                    costUSD: 2.5,                    requests: 3,                    inputTokens: 200,                    cachedInputTokens: 20,                    outputTokens: 30,                    totalTokens: 250,                    lineItems: [],                    models: []),                OpenAIAPIUsageSnapshot.DailyBucket(                    day: "2026-05-18",                    startTime: currentUTCStart,                    endTime: currentUTCStart.addingTimeInterval(86400),                    costUSD: 4.5,                    requests: 5,                    inputTokens: 400,                    cachedInputTokens: 40,                    outputTokens: 50,                    totalTokens: 490,                    lineItems: [],                    models: []),            ],            updatedAt: now)         let today = usage.summary(forLocalDayContaining: now, calendar: calendar)         #expect(today.costUSD == 4.5)        #expect(today.requests == 5)        #expect(today.totalTokens == 490)    }     @Test    func `Claude Admin current day includes UTC bucket containing positive timezone morning`() throws {        let calendar = try Self.calendar(timeZoneIdentifier: "Australia/Sydney")        let now = try Self.date(year: 2026, month: 5, day: 18, hour: 8, timeZoneIdentifier: "Australia/Sydney")        let staleUTCStart = try Self.date(year: 2026, month: 5, day: 16, hour: 0, timeZoneIdentifier: "UTC")        let overlappingUTCStart = try Self.date(year: 2026, month: 5, day: 17, hour: 0, timeZoneIdentifier: "UTC")        let usage = ClaudeAdminAPIUsageSnapshot(            daily: [                ClaudeAdminAPIUsageSnapshot.DailyBucket(                    day: "2026-05-16",                    startTime: staleUTCStart,                    endTime: staleUTCStart.addingTimeInterval(86400),                    costUSD: 9,                    inputTokens: 900,                    cacheCreationInputTokens: 90,                    cacheReadInputTokens: 45,                    outputTokens: 90,                    totalTokens: 1125,                    costItems: [],                    models: []),                ClaudeAdminAPIUsageSnapshot.DailyBucket(                    day: "2026-05-17",                    startTime: overlappingUTCStart,                    endTime: overlappingUTCStart.addingTimeInterval(86400),                    costUSD: 2.5,                    inputTokens: 200,                    cacheCreationInputTokens: 20,                    cacheReadInputTokens: 10,                    outputTokens: 30,                    totalTokens: 260,                    costItems: [],                    models: []),            ],            updatedAt: now)         let today = usage.summary(forLocalDayContaining: now, calendar: calendar)         #expect(today.costUSD == 2.5)        #expect(today.inputTokens == 200)        #expect(today.totalTokens == 260)    }     @Test    func `Claude Admin current day does not sum adjacent UTC buckets after negative timezone UTC rollover`() throws {        let calendar = try Self.calendar(timeZoneIdentifier: "America/Los_Angeles")        let now = try Self.date(year: 2026, month: 6, day: 22, hour: 20, timeZoneIdentifier: "America/Los_Angeles")        let previousUTCStart = try Self.date(year: 2026, month: 6, day: 22, hour: 0, timeZoneIdentifier: "UTC")        let currentUTCStart = try Self.date(year: 2026, month: 6, day: 23, hour: 0, timeZoneIdentifier: "UTC")        let usage = ClaudeAdminAPIUsageSnapshot(            daily: [                ClaudeAdminAPIUsageSnapshot.DailyBucket(                    day: "2026-06-22",                    startTime: previousUTCStart,                    endTime: previousUTCStart.addingTimeInterval(86400),                    costUSD: 2.5,                    inputTokens: 200,                    cacheCreationInputTokens: 20,                    cacheReadInputTokens: 10,                    outputTokens: 30,                    totalTokens: 260,                    costItems: [],                    models: []),                ClaudeAdminAPIUsageSnapshot.DailyBucket(                    day: "2026-06-23",                    startTime: currentUTCStart,                    endTime: currentUTCStart.addingTimeInterval(86400),                    costUSD: 4.5,                    inputTokens: 400,                    cacheCreationInputTokens: 40,                    cacheReadInputTokens: 20,                    outputTokens: 50,                    totalTokens: 510,                    costItems: [],                    models: []),            ],            updatedAt: now)         let today = usage.summary(forLocalDayContaining: now, calendar: calendar)         #expect(today.costUSD == 4.5)        #expect(today.inputTokens == 400)        #expect(today.totalTokens == 510)    }     private static func calendar(timeZoneIdentifier: String) throws -> Calendar {        var calendar = Calendar(identifier: .gregorian)        calendar.timeZone = try #require(TimeZone(identifier: timeZoneIdentifier))        return calendar    }     private static func date(        year: Int,        month: Int,        day: Int,        hour: Int,        timeZoneIdentifier: String) throws -> Date    {        var components = DateComponents()        components.calendar = Calendar(identifier: .gregorian)        components.timeZone = TimeZone(identifier: timeZoneIdentifier)        components.year = year        components.month = month        components.day = day        components.hour = hour        return try #require(components.date)    }}