Files
steipete--codexbar/Tests/CodexBarTests/CodexActiveSourceConfigTests.swift
T
2026-07-13 12:22:33 +08:00

243 lines
9.1 KiB
Swift

import CodexBarCore
import Foundation
import Testing
@Suite(.serialized)
struct CodexActiveSourceConfigTests {
@Test
func `legacy config without codex active source decodes to nil`() throws {
let legacyJSON = """
{
"version": 1,
"providers": [
{
"id": "codex"
}
]
}
"""
let decoded = try JSONDecoder().decode(
CodexBarConfig.self,
from: Data(legacyJSON.utf8))
#expect(decoded.providerConfig(for: .codex)?.codexActiveSource == nil)
#expect(decoded.providerConfig(for: .codex)?.quotaWarnings == nil)
}
@Test
func `provider config round trips quota warning overrides`() throws {
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
quotaWarnings: QuotaWarningConfig(
session: QuotaWarningWindowConfig(thresholds: [10]),
weekly: QuotaWarningWindowConfig(thresholds: [50, 20]))),
])
let data = try JSONEncoder().encode(config)
let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)
let quotaWarnings = try #require(decoded.providerConfig(for: .codex)?.quotaWarnings)
#expect(quotaWarnings.thresholds(for: .session, global: [80]) == [10])
#expect(quotaWarnings.thresholds(for: .weekly, global: [80]) == [50, 20])
}
@Test
func `quota warning window enabled defaults stay backward compatible`() throws {
let legacyJSON = """
{
"version": 1,
"providers": [
{
"id": "codex",
"quotaWarnings": {
"session": { "thresholds": [10] },
"weekly": { "enabled": false }
}
}
]
}
"""
let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: Data(legacyJSON.utf8))
let quotaWarnings = try #require(decoded.providerConfig(for: .codex)?.quotaWarnings)
#expect(quotaWarnings.isEnabled(for: .session, global: false) == true)
#expect(quotaWarnings.isEnabled(for: .weekly, global: true) == false)
#expect(quotaWarnings.hasOverride(for: .session) == true)
#expect(quotaWarnings.hasOverride(for: .weekly) == true)
}
@Test
func `provider config encodes live system active source with expected schema`() throws {
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
codexActiveSource: .liveSystem),
])
let data = try JSONEncoder().encode(config)
let object = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let providers = try #require(object?["providers"] as? [[String: Any]])
let provider = try #require(providers.first(where: { $0["id"] as? String == "codex" }))
let activeSource = try #require(provider["codexActiveSource"] as? [String: Any])
#expect(activeSource.count == 1)
#expect(activeSource["kind"] as? String == "liveSystem")
#expect(activeSource["accountID"] == nil)
}
@Test
func `provider config encodes managed account active source with expected schema`() throws {
let accountID = UUID()
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
codexActiveSource: .managedAccount(id: accountID)),
])
let data = try JSONEncoder().encode(config)
let object = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let providers = try #require(object?["providers"] as? [[String: Any]])
let provider = try #require(providers.first(where: { $0["id"] as? String == "codex" }))
let activeSource = try #require(provider["codexActiveSource"] as? [String: Any])
#expect(activeSource.count == 2)
#expect(activeSource["kind"] as? String == "managedAccount")
#expect((activeSource["accountID"] as? String) == accountID.uuidString)
}
@Test
func `provider config encodes profile home source in downgrade readable envelope`() throws {
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
codexActiveSource: .profileHome(path: "/Users/test/.codex-work"),
codexProfileHomePaths: ["/Users/test/.codex-work"]),
])
let data = try JSONEncoder().encode(config)
let object = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let providers = try #require(object?["providers"] as? [[String: Any]])
let provider = try #require(providers.first(where: { $0["id"] as? String == "codex" }))
let activeSource = try #require(provider["codexActiveSource"] as? [String: Any])
#expect(activeSource.count == 2)
#expect(activeSource["kind"] as? String == "liveSystem")
#expect(activeSource["homePath"] as? String == "/Users/test/.codex-work")
#expect(provider["codexProfileHomePaths"] as? [String] == ["/Users/test/.codex-work"])
let releasedConfig = try JSONDecoder().decode(ReleasedCodexBarConfig.self, from: data)
#expect(releasedConfig.providers.first?.codexActiveSource == .liveSystem)
}
@Test
func `provider config round trips live system active source`() throws {
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
codexActiveSource: .liveSystem),
])
let data = try JSONEncoder().encode(config)
let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)
#expect(decoded.providerConfig(for: .codex)?.codexActiveSource == .liveSystem)
}
@Test
func `provider config round trips managed account active source`() throws {
let accountID = UUID()
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
codexActiveSource: .managedAccount(id: accountID)),
])
let data = try JSONEncoder().encode(config)
let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)
#expect(decoded.providerConfig(for: .codex)?.codexActiveSource == .managedAccount(id: accountID))
}
@Test
func `provider config round trips profile home active source`() throws {
let config = CodexBarConfig(
providers: [
ProviderConfig(
id: .codex,
codexActiveSource: .profileHome(path: "/Users/test/.codex-work"),
codexProfileHomePaths: ["/Users/test/.codex-work"]),
])
let data = try JSONEncoder().encode(config)
let decoded = try JSONDecoder().decode(CodexBarConfig.self, from: data)
let providerConfig = decoded.providerConfig(for: .codex)
#expect(providerConfig?.codexActiveSource == .profileHome(path: "/Users/test/.codex-work"))
#expect(providerConfig?.codexProfileHomePaths == ["/Users/test/.codex-work"])
}
@Test
func `profile home discriminator written by development builds still decodes`() throws {
let data = Data(#"{"kind":"profileHome","homePath":"/Users/test/.codex-work"}"#.utf8)
let decoded = try JSONDecoder().decode(CodexActiveSource.self, from: data)
let canonicalData = try JSONEncoder().encode(decoded)
let canonical = try #require(JSONSerialization.jsonObject(with: canonicalData) as? [String: Any])
#expect(decoded == .profileHome(path: "/Users/test/.codex-work"))
#expect(canonical["kind"] as? String == "liveSystem")
#expect(canonical["homePath"] as? String == "/Users/test/.codex-work")
}
@Test
func `blank profile home sentinel falls back to live system`() throws {
let data = Data(#"{"kind":"liveSystem","homePath":" "}"#.utf8)
let decoded = try JSONDecoder().decode(CodexActiveSource.self, from: data)
#expect(decoded == .liveSystem)
}
}
private struct ReleasedCodexBarConfig: Decodable {
let providers: [ReleasedProviderConfig]
}
private struct ReleasedProviderConfig: Decodable {
let codexActiveSource: ReleasedCodexActiveSource?
}
private enum ReleasedCodexActiveSource: Decodable, Equatable {
case liveSystem
case managedAccount(id: UUID)
private enum CodingKeys: String, CodingKey {
case kind
case accountID
}
private enum Kind: String, Decodable {
case liveSystem
case managedAccount
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch try container.decode(Kind.self, forKey: .kind) {
case .liveSystem:
self = .liveSystem
case .managedAccount:
self = try .managedAccount(id: container.decode(UUID.self, forKey: .accountID))
}
}
}