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

428 lines
17 KiB
Swift

import CodexBarCore
import Foundation
import Testing
struct ConfigValidationTests {
@Test
func `fresh config defaults Alibaba Token Plan to International`() throws {
let config = CodexBarConfig.makeDefault()
let provider = try #require(config.providerConfig(for: .alibabatokenplan))
let issues = CodexBarConfigValidator.validate(config)
#expect(provider.region == AlibabaTokenPlanAPIRegion.international.rawValue)
#expect(!issues.contains(where: { $0.provider == .alibabatokenplan }))
}
@Test
func `normalization preserves legacy Alibaba Token Plan region`() throws {
let config = CodexBarConfig(providers: [
ProviderConfig(id: .alibabatokenplan, region: nil),
]).normalized()
let provider = try #require(config.providerConfig(for: .alibabatokenplan))
#expect(provider.region == nil)
}
@Test
func `normalization adds missing Alibaba Token Plan as China mainland`() throws {
let config = CodexBarConfig(providers: [
ProviderConfig(id: .codex),
]).normalized()
let provider = try #require(config.providerConfig(for: .alibabatokenplan))
#expect(provider.region == AlibabaTokenPlanAPIRegion.chinaMainland.rawValue)
}
@Test
func `reports invalid Alibaba Token Plan region`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .alibabatokenplan, region: "nowhere"))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: {
$0.provider == .alibabatokenplan && $0.code == "invalid_region"
}))
}
@Test
func `reports unsupported source`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .codex, source: .api))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.code == "unsupported_source" }))
}
@Test
func `accepts legacy factory cli source as compatibility alias`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .factory, source: .cli))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: {
$0.provider == .factory && $0.code == "unsupported_source"
}))
#expect(FactoryProviderDescriptor.descriptor.fetchPlan.sourceModes.contains(.cli))
}
@Test
func `reports missing API key when source API`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .zai, source: .api, apiKey: nil))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.code == "api_key_missing" }))
}
@Test
func `allows credentialless Wayfinder API source`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .wayfinder,
source: .api,
enterpriseHost: "http://127.0.0.1:9191"))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .wayfinder && $0.code == "api_key_missing" }))
}
@Test
func `sub2api token accounts satisfy API credentials`() {
let accounts = ProviderTokenAccountData(
version: 1,
accounts: [
ProviderTokenAccount(
id: UUID(),
label: "Primary",
token: "fixture",
addedAt: 0,
lastUsed: nil),
],
activeIndex: 0)
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .sub2api,
source: .api,
enterpriseHost: "https://sub2api.example.com",
tokenAccounts: accounts))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .sub2api && $0.code == "api_key_missing" }))
}
@Test
func `sub2api accepts HTTPS and loopback HTTP base URLs`() {
for host in ["https://sub2api.example.com", "http://127.0.0.1:8080"] {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .sub2api,
source: .api,
apiKey: "fixture",
enterpriseHost: host))
let invalidHostIssue = CodexBarConfigValidator.validate(config).first { issue in
issue.provider == .sub2api && issue.code == "invalid_enterprise_host"
}
#expect(invalidHostIssue == nil)
}
}
@Test
func `sub2api rejects unsafe base URLs`() {
let invalidHosts = [
"http://sub2api.example.com",
"https://user:pass@sub2api.example.com",
"https://sub2api.example.com?token=secret",
"https://sub2api.example.com#fragment",
]
for host in invalidHosts {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .sub2api,
source: .api,
apiKey: "fixture",
enterpriseHost: host))
let invalidHostIssue = CodexBarConfigValidator.validate(config).first { issue in
issue.provider == .sub2api &&
issue.field == "enterpriseHost" &&
issue.code == "invalid_enterprise_host"
}
#expect(invalidHostIssue != nil)
}
}
@Test
func `sub2api rejects blank token accounts as API credentials`() {
let accounts = ProviderTokenAccountData(
version: 1,
accounts: [
ProviderTokenAccount(
id: UUID(),
label: "Blank",
token: " ",
addedAt: 0,
lastUsed: nil),
],
activeIndex: 0)
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .sub2api,
source: .api,
enterpriseHost: "https://sub2api.example.com",
tokenAccounts: accounts))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.provider == .sub2api && $0.code == "api_key_missing" }))
}
@Test
func `reports invalid region`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .minimax, region: "nowhere"))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.code == "invalid_region" }))
}
@Test
func `warns on unsupported token accounts`() {
let accounts = ProviderTokenAccountData(
version: 1,
accounts: [ProviderTokenAccount(id: UUID(), label: "a", token: "t", addedAt: 0, lastUsed: nil)],
activeIndex: 0)
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .gemini, tokenAccounts: accounts))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.code == "token_accounts_unused" }))
}
@Test
func `allows ollama token accounts`() {
let accounts = ProviderTokenAccountData(
version: 1,
accounts: [ProviderTokenAccount(id: UUID(), label: "a", token: "t", addedAt: 0, lastUsed: nil)],
activeIndex: 0)
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .ollama, tokenAccounts: accounts))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.code == "token_accounts_unused" && $0.provider == .ollama }))
}
@Test
func `accepts kilo extras config field`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .kilo, extrasEnabled: true))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .kilo && $0.field == "extrasEnabled" }))
}
@Test
func `allows deepgram project workspace ID`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .deepgram, workspaceID: "project-123"))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .deepgram && $0.code == "workspace_unused" }))
}
@Test
func `allows Azure OpenAI endpoint and deployment fields`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .azureopenai,
workspaceID: "chat-prod",
enterpriseHost: "https://example-resource.openai.azure.com"))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .azureopenai && $0.code == "workspace_unused" }))
#expect(!issues.contains(where: { $0.provider == .azureopenai && $0.code == "enterprise_host_unused" }))
}
@Test
func `allows LiteLLM endpoint`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .litellm,
apiKey: "sk-test",
enterpriseHost: "https://litellm.example.com"))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .litellm && $0.code == "enterprise_host_unused" }))
}
@Test
func `unsupported enterprise host warning lists every supported provider`() throws {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .gemini, enterpriseHost: "https://example.com"))
let issue = try #require(CodexBarConfigValidator.validate(config).first(where: {
$0.provider == .gemini && $0.code == "enterprise_host_unused"
}))
#expect(issue.message ==
"enterpriseHost is set but only azureopenai, clawrouter, copilot, kimi, litellm, llmproxy, sub2api, and " +
"wayfinder " +
"support enterpriseHost.")
}
@Test
func `allows OpenAI API project workspace ID`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .openai, workspaceID: "proj_abc"))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .openai && $0.code == "workspace_unused" }))
}
@Test
func `allows doubao coding plan credential fields`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(
id: .doubao,
apiKey: "AKLT-config",
secretKey: "sk-config",
region: "cn-shanghai"))
let issues = CodexBarConfigValidator.validate(config)
#expect(!issues.contains(where: { $0.provider == .doubao && $0.code == "secret_key_unused" }))
#expect(!issues.contains(where: { $0.provider == .doubao && $0.code == "region_unused" }))
}
@Test
func `warns when zai team token account is missing BigModel context`() {
let accounts = ProviderTokenAccountData(
version: 1,
accounts: [
ProviderTokenAccount(
id: UUID(),
label: "Team",
token: "token",
addedAt: 0,
lastUsed: nil,
usageScope: "team",
organizationID: "org_abc"),
],
activeIndex: 0)
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .zai, tokenAccounts: accounts))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.provider == .zai && $0.code == "zai_team_context_missing" }))
}
@Test
func `warns on unsupported workspace ID`() {
var config = CodexBarConfig.makeDefault()
config.setProviderConfig(ProviderConfig(id: .gemini, workspaceID: "workspace-123"))
let issues = CodexBarConfigValidator.validate(config)
#expect(issues.contains(where: { $0.provider == .gemini && $0.code == "workspace_unused" }))
#expect(issues.contains(where: { issue in
issue.provider == .gemini &&
issue.code == "workspace_unused" &&
issue.message.contains("openai")
}))
}
@Test
func `config store default url honors environment override`() {
let url = CodexBarConfigStore.defaultURL(environment: [
CodexBarConfigStore.pathEnvironmentKey: "~/tmp/codexbar-test-config.json",
])
#expect(url.path.hasSuffix("/tmp/codexbar-test-config.json"))
}
@Test
func `config store default url honors xdg config home`() throws {
let fileManager = FileManager.default
let home = try Self.makeTemporaryHome()
defer { try? fileManager.removeItem(at: home) }
let xdgHome = home.appendingPathComponent("custom-config", isDirectory: true)
let url = CodexBarConfigStore.defaultURL(
home: home,
environment: [
CodexBarConfigStore.xdgConfigHomeEnvironmentKey: xdgHome.path,
],
fileManager: fileManager)
#expect(url == Self.configURL(in: xdgHome))
}
@Test
func `config store default url ignores relative xdg config home`() throws {
let fileManager = FileManager.default
let home = try Self.makeTemporaryHome()
defer { try? fileManager.removeItem(at: home) }
let legacy = Self.legacyConfigURL(in: home)
try Self.touch(legacy, fileManager: fileManager)
let url = CodexBarConfigStore.defaultURL(
home: home,
environment: [
CodexBarConfigStore.xdgConfigHomeEnvironmentKey: "relative-config",
],
fileManager: fileManager)
#expect(url == legacy)
}
@Test
func `config store default url creates in xdg default for new installs`() throws {
let fileManager = FileManager.default
let home = try Self.makeTemporaryHome()
defer { try? fileManager.removeItem(at: home) }
let url = CodexBarConfigStore.defaultURL(home: home, environment: [:], fileManager: fileManager)
#expect(url == Self.configURL(in: home.appendingPathComponent(".config", isDirectory: true)))
}
@Test
func `config store default url keeps existing legacy config`() throws {
let fileManager = FileManager.default
let home = try Self.makeTemporaryHome()
defer { try? fileManager.removeItem(at: home) }
let legacy = Self.legacyConfigURL(in: home)
try Self.touch(legacy, fileManager: fileManager)
let url = CodexBarConfigStore.defaultURL(home: home, environment: [:], fileManager: fileManager)
#expect(url == legacy)
}
@Test
func `config store default url prefers existing xdg default over legacy config`() throws {
let fileManager = FileManager.default
let home = try Self.makeTemporaryHome()
defer { try? fileManager.removeItem(at: home) }
let xdgDefault = Self.configURL(in: home.appendingPathComponent(".config", isDirectory: true))
let legacy = Self.legacyConfigURL(in: home)
try Self.touch(legacy, fileManager: fileManager)
try Self.touch(xdgDefault, fileManager: fileManager)
let url = CodexBarConfigStore.defaultURL(home: home, environment: [:], fileManager: fileManager)
#expect(url == xdgDefault)
}
private static func makeTemporaryHome() throws -> URL {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("CodexBarConfigStoreTests-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
return url
}
private static func touch(_ url: URL, fileManager: FileManager) throws {
try fileManager.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
try Data().write(to: url)
}
private static func configURL(in directory: URL) -> URL {
directory
.appendingPathComponent("codexbar", isDirectory: true)
.appendingPathComponent("config.json")
}
private static func legacyConfigURL(in home: URL) -> URL {
home
.appendingPathComponent(".codexbar", isDirectory: true)
.appendingPathComponent("config.json")
}
}