Files
2026-07-13 12:22:33 +08:00

237 lines
6.7 KiB
Swift

import CodexBarCore
import Foundation
@testable import CodexBar
#if os(macOS)
import AppKit
#endif
final class InMemoryCookieHeaderStore: CookieHeaderStoring, @unchecked Sendable {
var value: String?
init(value: String? = nil) {
self.value = value
}
func loadCookieHeader() throws -> String? {
self.value
}
func storeCookieHeader(_ header: String?) throws {
self.value = header
}
}
final class InMemoryMiniMaxCookieStore: MiniMaxCookieStoring, @unchecked Sendable {
var value: String?
init(value: String? = nil) {
self.value = value
}
func loadCookieHeader() throws -> String? {
self.value
}
func storeCookieHeader(_ header: String?) throws {
self.value = header
}
}
final class InMemoryMiniMaxAPITokenStore: MiniMaxAPITokenStoring, @unchecked Sendable {
var value: String?
init(value: String? = nil) {
self.value = value
}
func loadToken() throws -> String? {
self.value
}
func storeToken(_ token: String?) throws {
self.value = token
}
}
final class InMemoryKimiTokenStore: KimiTokenStoring, @unchecked Sendable {
var value: String?
init(value: String? = nil) {
self.value = value
}
func loadToken() throws -> String? {
self.value
}
func storeToken(_ token: String?) throws {
self.value = token
}
}
final class InMemoryKimiK2TokenStore: KimiK2TokenStoring, @unchecked Sendable {
var value: String?
init(value: String? = nil) {
self.value = value
}
func loadToken() throws -> String? {
self.value
}
func storeToken(_ token: String?) throws {
self.value = token
}
}
final class InMemoryCopilotTokenStore: CopilotTokenStoring, @unchecked Sendable {
var value: String?
init(value: String? = nil) {
self.value = value
}
func loadToken() throws -> String? {
self.value
}
func storeToken(_ token: String?) throws {
self.value = token
}
}
final class InMemoryTokenAccountStore: ProviderTokenAccountStoring, @unchecked Sendable {
var accounts: [UsageProvider: ProviderTokenAccountData] = [:]
private let fileURL: URL
init(fileURL: URL = FileManager.default.temporaryDirectory.appendingPathComponent(
"token-accounts-\(UUID().uuidString).json"))
{
self.fileURL = fileURL
}
func loadAccounts() throws -> [UsageProvider: ProviderTokenAccountData] {
self.accounts
}
func storeAccounts(_ accounts: [UsageProvider: ProviderTokenAccountData]) throws {
self.accounts = accounts
}
func ensureFileExists() throws -> URL {
self.fileURL
}
}
func testConfigStore(suiteName: String, reset: Bool = true) -> CodexBarConfigStore {
let sanitized = suiteName.replacingOccurrences(of: "/", with: "-")
let base = FileManager.default.temporaryDirectory
.appendingPathComponent("codexbar-tests", isDirectory: true)
.appendingPathComponent(sanitized, isDirectory: true)
let url = base.appendingPathComponent("config.json")
if reset {
try? FileManager.default.removeItem(at: url)
}
return CodexBarConfigStore(fileURL: url)
}
@MainActor
func testSettingsStore(
suiteName: String,
tokenAccountStore: any ProviderTokenAccountStoring = InMemoryTokenAccountStore(),
config: CodexBarConfig? = nil) -> SettingsStore
{
let isolatedSuiteName = "\(suiteName)-\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: isolatedSuiteName) else {
preconditionFailure("Could not create test defaults suite")
}
defaults.removePersistentDomain(forName: isolatedSuiteName)
let configStore = testConfigStore(suiteName: isolatedSuiteName)
if let config {
do {
try configStore.save(config)
} catch {
preconditionFailure("Could not save test config: \(error)")
}
}
return SettingsStore(
userDefaults: defaults,
configStore: configStore,
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore(),
codexCookieStore: InMemoryCookieHeaderStore(),
claudeCookieStore: InMemoryCookieHeaderStore(),
cursorCookieStore: InMemoryCookieHeaderStore(),
opencodeCookieStore: InMemoryCookieHeaderStore(),
factoryCookieStore: InMemoryCookieHeaderStore(),
minimaxCookieStore: InMemoryMiniMaxCookieStore(),
minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),
kimiTokenStore: InMemoryKimiTokenStore(),
kimiK2TokenStore: InMemoryKimiK2TokenStore(),
augmentCookieStore: InMemoryCookieHeaderStore(),
ampCookieStore: InMemoryCookieHeaderStore(),
copilotTokenStore: InMemoryCopilotTokenStore(),
tokenAccountStore: tokenAccountStore)
}
#if os(macOS)
@MainActor
func testStatusBar() -> NSStatusBar {
// Standalone NSStatusBar instances can crash during swiftpm-testing-helper teardown.
.system
}
@MainActor
@discardableResult
func withStatusItemControllerForTesting<T>(
store: UsageStore,
settings: SettingsStore,
fetcher: UsageFetcher,
statusBar: NSStatusBar = .system,
operation: (StatusItemController) throws -> T) rethrows -> T
{
let controller = StatusItemController(
store: store,
settings: settings,
account: fetcher.loadAccountInfo(),
updater: DisabledUpdaterController(),
preferencesSelection: PreferencesSelection(),
statusBar: statusBar)
defer { controller.releaseStatusItemsForTesting() }
return try operation(controller)
}
@MainActor
@discardableResult
func withStatusItemControllerForTesting<T>(
store: UsageStore,
settings: SettingsStore,
fetcher: UsageFetcher,
statusBar: NSStatusBar = .system,
operation: (StatusItemController) async throws -> T) async rethrows -> T
{
let controller = StatusItemController(
store: store,
settings: settings,
account: fetcher.loadAccountInfo(),
updater: DisabledUpdaterController(),
preferencesSelection: PreferencesSelection(),
statusBar: statusBar)
defer { controller.releaseStatusItemsForTesting() }
return try await operation(controller)
}
#endif
func testPlanUtilizationHistoryStore(suiteName: String, reset: Bool = true) -> PlanUtilizationHistoryStore {
let sanitized = suiteName.replacingOccurrences(of: "/", with: "-")
let base = FileManager.default.temporaryDirectory
.appendingPathComponent("codexbar-tests", isDirectory: true)
.appendingPathComponent(sanitized, isDirectory: true)
let url = base.appendingPathComponent("history", isDirectory: true)
if reset {
try? FileManager.default.removeItem(at: url)
}
return PlanUtilizationHistoryStore(directoryURL: url)
}