Files
openless/Tests/OpenLessCoreTests/LLMProviderRegistryTests.swift
T
baiqing d8fb309feb feat(persistence): generalize providers.llm + multi-provider vault API
将 v1 schema 中 `providers.llm` 从 Ark 专用结构推广为通用
`CredentialsProviderLLMEntry`(containing displayName / temperature / extraHeaders),
为 OpenAI / DeepSeek / DashScope / Moonshot 等多 LLM provider 共用一套字段。

旧 `CredentialsProviderLLMArk` 保留为 typealias,已迁移过的 v1 文件无需再次迁移即可继续解码。

新增公开 API:
- `CredentialsVault.activeLLMProviderId` getter/setter
- `CredentialsVault.configuredLLMProviderIds`
- `CredentialsVault.llmProviderConfig(for:)`
- `CredentialsVault.setLLMProviderConfig(_:)`
- `CredentialsVault.removeLLMProvider(_:)`,禁删 active provider

`OpenLessCore` 新增 `LLMProviderRegistry`,集中维护 5 家 OpenAI 兼容供应商
预设(baseURL / model / 帮助文案 / docs URL),UI 与 vault 兜底逻辑共用。

`PolishOutputCleaner` 从 DoubaoPolishClient 内部提升为独立 public 类型,
让 `OpenAICompatibleLLMProvider` 输出统一过 boilerplate / markdown 栅栏清理。

DoubaoPolishClient.swift 已无调用方,整个文件删除。

Tests:
- LLMProviderRegistryTests:5 家预设健康检查 + 唯一性 / URL 合法性
- CredentialsVaultMultiProviderTests:active 默认 / 增删 / 往返 /
  老 `ark.*` 账号 key 路由到 active provider
2026-04-29 11:54:11 +08:00

87 lines
3.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import XCTest
@testable import OpenLessCore
/// LLMProviderRegistry 的静态健康检查:5 家供应商各 1 条,id 不重复,URL 合法。
///
/// 这些断言看起来很无聊,但 registry 直接被设置 UI 和 vault 兜底逻辑用,
/// 漏一条都会变成"用户加不了 provider / baseURL 解不出来"的运行期 bug。
final class LLMProviderRegistryTests: XCTestCase {
func test_presets_haveExpectedCount() {
// Arrange
let presets = LLMProviderRegistry.presets
// Act / Assert
XCTAssertEqual(presets.count, 5, "B-3 计划列出的预设是 ark / openai / aliyun-dashscope / deepseek / moonshot")
}
func test_presets_includeAllExpectedProviderIds() {
// Arrange
let expected: Set<String> = ["ark", "openai", "aliyun-dashscope", "deepseek", "moonshot"]
// Act
let actualIds = Set(LLMProviderRegistry.presets.map(\.providerId))
// Assert
XCTAssertEqual(actualIds, expected)
}
func test_presets_haveUniqueProviderIds() {
// Arrange
let ids = LLMProviderRegistry.presets.map(\.providerId)
// Act / Assert
XCTAssertEqual(Set(ids).count, ids.count, "providerId 必须唯一,否则 vault 会被互相覆盖")
}
func test_presets_haveValidBaseURLs() {
for preset in LLMProviderRegistry.presets {
// Arrange
let url = preset.defaultBaseURL
// Act / Assert:URL 必须是 https + 有 host
XCTAssertEqual(url.scheme, "https", "preset \(preset.providerId) 的 baseURL 必须是 https,避免网络层降级")
XCTAssertNotNil(url.host, "preset \(preset.providerId) 的 baseURL 必须有 host")
}
}
func test_arkPreset_hasEmptyDefaultModel() {
// Ark 的 defaultModel 故意留空——endpoint id 是用户在控制台拿到的 hash,
// 不能预设。如果哪天默认填了一个看起来像 model 的字符串,UI 会误导用户。
let ark = LLMProviderRegistry.preset(for: "ark")
XCTAssertNotNil(ark)
XCTAssertEqual(ark?.defaultModel, "")
}
func test_nonArkPresets_haveDefaultModel() {
// OpenAI / DeepSeek / Moonshot / DashScope 都给了一个开箱即用的 model。
for preset in LLMProviderRegistry.presets where preset.providerId != "ark" {
XCTAssertFalse(preset.defaultModel.isEmpty, "preset \(preset.providerId) 应当有 defaultModel")
}
}
func test_presets_haveNonEmptyHelpText() {
for preset in LLMProviderRegistry.presets {
XCTAssertFalse(preset.helpText.isEmpty, "preset \(preset.providerId) 的 helpText 不能为空")
}
}
func test_preset_lookup_returnsRegistered() {
for preset in LLMProviderRegistry.presets {
let resolved = LLMProviderRegistry.preset(for: preset.providerId)
XCTAssertEqual(resolved, preset)
}
}
func test_preset_lookup_unknownReturnsNil() {
XCTAssertNil(LLMProviderRegistry.preset(for: "no-such-provider-xyz"))
}
func test_customSentinels_areDistinctFromPresetIds() {
// 添加 sheet 用 customProviderId 区分"预设 vs 自定义"——它必须不在预设列表里,
// 否则会被点击事件混淆。
let presetIds = Set(LLMProviderRegistry.presets.map(\.providerId))
XCTAssertFalse(presetIds.contains(LLMProviderRegistry.customProviderId))
}
}