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.
81 lines
3.3 KiB
Swift
81 lines
3.3 KiB
Swift
import AgentKit
|
|
import Foundation
|
|
import TermindApplication
|
|
import Testing
|
|
|
|
@Suite @MainActor
|
|
struct ModelProviderCredentialTests {
|
|
@Test func anonymousProvidersIgnoreUnrelatedSecretFailuresWithoutReadingStorage() {
|
|
let secrets = ProviderTestSecrets()
|
|
secrets.failReads = true
|
|
let ai = AIConfigurationModel(aiSecrets: secrets)
|
|
#expect(ai.secret(for: "unavailable") == nil)
|
|
#expect(ai.secretStorageError != nil)
|
|
let provider = ModelProvider(name: "Anonymous", models: [AIModel(id: "test")])
|
|
ai.upsert(provider)
|
|
#expect(ai.modelSecret(for: provider) == "")
|
|
#expect(CommandGeneratorController(ai: ai).isLocallyAvailable)
|
|
#expect(secrets.reads == ["unavailable"])
|
|
#expect(secrets.values.isEmpty)
|
|
}
|
|
|
|
@Test func configuredReferencesNeverFallBackToAnonymousRequests() {
|
|
let secrets = ProviderTestSecrets()
|
|
let ai = AIConfigurationModel(aiSecrets: secrets)
|
|
let provider = ModelProvider(name: "Authenticated", credentialRef: "key", models: [AIModel(id: "test")])
|
|
ai.upsert(provider)
|
|
let command = CommandGeneratorController(ai: ai)
|
|
#expect(ai.modelSecret(for: provider) == nil)
|
|
#expect(!command.isLocallyAvailable)
|
|
secrets.values["key"] = ""
|
|
#expect(ai.modelSecret(for: provider) == nil)
|
|
secrets.values["key"] = "test-key"
|
|
#expect(ai.modelSecret(for: provider) == "test-key")
|
|
#expect(command.isLocallyAvailable)
|
|
secrets.failReads = true
|
|
#expect(ai.modelSecret(for: provider) == nil)
|
|
#expect(!command.isLocallyAvailable)
|
|
}
|
|
|
|
@Test func vertexResolvesOnlyItsServiceAccountCredential() {
|
|
let secrets = ProviderTestSecrets()
|
|
let ai = AIConfigurationModel(aiSecrets: secrets)
|
|
var provider = ModelProvider(
|
|
name: "Vertex", apiFormat: .generateContent, credentialRef: "ordinary", usesVertex: true)
|
|
secrets.values["ordinary"] = "wrong-key"
|
|
#expect(ai.modelSecret(for: provider) == nil)
|
|
provider.vertex.credentialRef = "service-account"
|
|
#expect(ai.modelSecret(for: provider) == nil)
|
|
secrets.values["service-account"] = "service-account-json"
|
|
#expect(ai.modelSecret(for: provider) == "service-account-json")
|
|
}
|
|
|
|
@Test func clearingAKeyEnablesAnonymousResolutionWithoutCreatingASecret() {
|
|
let secrets = ProviderTestSecrets()
|
|
let ai = AIConfigurationModel(aiSecrets: secrets)
|
|
var provider = ModelProvider(name: "Gateway", credentialRef: "key")
|
|
ai.setSecret("test-key", for: "key")
|
|
#expect(ai.modelSecret(for: provider) == "test-key")
|
|
ai.setSecret(nil, for: "key")
|
|
#expect(ai.modelSecret(for: provider) == nil)
|
|
provider.credentialRef = ""
|
|
#expect(ai.modelSecret(for: provider) == "")
|
|
#expect(secrets.values.isEmpty)
|
|
}
|
|
}
|
|
|
|
@MainActor private final class ProviderTestSecrets: AISecretStoring {
|
|
enum Failure: Error { case unavailable }
|
|
var values: [String: String] = [:]
|
|
var reads: [String] = []
|
|
var failReads = false
|
|
|
|
func string(for reference: String) throws -> String? {
|
|
reads.append(reference)
|
|
if failReads { throw Failure.unavailable }
|
|
return values[reference]
|
|
}
|
|
|
|
func setString(_ value: String?, for reference: String) throws { values[reference] = value }
|
|
}
|