Files
Termind/TFMTests/ProviderEndpointIntegrationTests.swift
T
Kaede Akino 4ea707083f Support keyless model providers and HTTP endpoints
Allow optional API keys in desktop and mobile settings and share credential
resolution across model requests. Missing stored credentials remain errors.
Enable user-configured HTTP endpoints and pin signed AgentKit 0.13.1, which
omits empty authentication headers for probes and inference.

Validation: AgentKit's 216 tests and iOS's 461 tests passed, along with Swift
formatting, localization checks, native screenshots, and real HTTP smoke tests
on both apps. macOS passed 1419 of 1422 tests initially; existing Metrics timing,
waveform PNG comparison, and MCP upload runner failures were observed. MCP and
Metrics passed a focused rerun, while Metrics remains intermittent and waveform
comparison still fails. iOS simulator LAN-IP access timed out; desktop LAN-IP
and both platforms' localhost, loopback-IP, and local-domain requests passed.
2026-09-17 08:52:16 +08:00

77 lines
3.3 KiB
Swift

import AgentKit
import SwiftData
import TermindApplication
import TermindDomain
import TermindInfrastructure
import TermindPersistence
import TermindPresentation
import TermindRemoteInfrastructure
import Testing
@testable import Termind
@Suite @MainActor
struct ProviderEndpointIntegrationTests {
@Test func anonymousHTTPProviderSurvivesPersistenceReload() throws {
let container = try ModelContainer(
for: HostPersistence.schema,
configurations: [ModelConfiguration(isStoredInMemoryOnly: true, cloudKitDatabase: .none)])
let provider = ModelProvider(name: "Local", models: [AIModel(id: "local-model")])
let store = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
#expect(store.saveProvider(provider, endpoint: ProviderEndpoint(url: "http://localhost:11434/v1")))
let reloaded = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
let saved = try #require(reloaded.modelProvider(provider.id))
#expect(saved.credentialRef.isEmpty)
#expect(saved.inferenceURL == "http://localhost:11434/v1/chat/completions")
#expect(saved.models.map(\.id) == ["local-model"])
#expect(saved.canFetchModels)
#expect(reloaded.modelSecret(for: saved) == "")
#expect(CommandGeneratorController(ai: reloaded).isLocallyAvailable)
}
/// The round trip the editor depends on: what was typed comes back, and so
/// does the difference between `Automatic` and picking the same format.
@Test
func endpointsSurviveAReloadIncludingTheAutomaticChoice() throws {
let container = try ModelContainer(
for: HostPersistence.schema,
configurations: [
ModelConfiguration(
"EndpointRoundTrip", schema: HostPersistence.schema,
isStoredInMemoryOnly: true, cloudKitDatabase: .none
)
]
)
let automatic = ModelProvider(name: "Automatic")
let explicit = ModelProvider(name: "Explicit")
do {
let store = AIConfigurationModel(
modelContext: container.mainContext, persistsSecrets: false
)
store.upsert(automatic)
store.upsert(explicit)
store.setEndpoint(ProviderEndpoint(url: "https://x.test/v1"), for: automatic.id)
store.setEndpoint(
ProviderEndpoint(apiType: .messages, url: "https://x.test/v1"), for: explicit.id
)
#expect(store.storageError == nil)
}
let reloaded = AIConfigurationModel(
modelContext: container.mainContext, persistsSecrets: false
)
#expect(reloaded.endpoint(for: automatic.id).apiType == .automatic)
#expect(reloaded.endpoint(for: automatic.id).url == "https://x.test/v1")
#expect(reloaded.modelProvider(automatic.id)?.apiFormat == .chatCompletions)
#expect(
reloaded.modelProvider(automatic.id)?.inferenceURL == "https://x.test/v1/chat/completions"
)
#expect(reloaded.endpoint(for: explicit.id).apiType == .messages)
#expect(reloaded.endpoint(for: explicit.id).url == "https://x.test/v1")
#expect(reloaded.modelProvider(explicit.id)?.inferenceURL == "https://x.test/v1/messages")
}
}