SwiftDBAI: natural language queries for any SQLite database
Drop-in SwiftUI chat view, headless ChatEngine, LLM-agnostic via AnyLanguageModel. Read-only by default with configurable allowlists. Robust SQL parser with 63 tests. Includes demo app with GitHub stars dataset.
This commit is contained in:
325
Tests/SwiftDBAITests/ProviderConfigurationTests.swift
Normal file
325
Tests/SwiftDBAITests/ProviderConfigurationTests.swift
Normal file
@@ -0,0 +1,325 @@
|
||||
// ProviderConfigurationTests.swift
|
||||
// SwiftDBAI Tests
|
||||
//
|
||||
// Tests for ProviderConfiguration — verifying all cloud provider configurations
|
||||
// produce valid LanguageModel instances with correct settings.
|
||||
|
||||
import AnyLanguageModel
|
||||
import Foundation
|
||||
@testable import SwiftDBAI
|
||||
import Testing
|
||||
|
||||
@Suite("ProviderConfiguration")
|
||||
struct ProviderConfigurationTests {
|
||||
|
||||
// MARK: - OpenAI Configuration
|
||||
|
||||
@Test("OpenAI configuration stores provider and model")
|
||||
func openAIBasicConfiguration() {
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: "sk-test-key-123",
|
||||
model: "gpt-4o"
|
||||
)
|
||||
|
||||
#expect(config.provider == .openAI)
|
||||
#expect(config.model == "gpt-4o")
|
||||
#expect(config.apiKey == "sk-test-key-123")
|
||||
#expect(config.hasValidAPIKey)
|
||||
}
|
||||
|
||||
@Test("OpenAI configuration produces a valid LanguageModel")
|
||||
func openAIMakeModel() {
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: "sk-test-key",
|
||||
model: "gpt-4o-mini"
|
||||
)
|
||||
|
||||
let model = config.makeModel()
|
||||
#expect(model is OpenAILanguageModel)
|
||||
}
|
||||
|
||||
@Test("OpenAI with custom base URL for compatible services")
|
||||
func openAICustomBaseURL() {
|
||||
let customURL = URL(string: "https://my-proxy.example.com/v1/")!
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: "sk-proxy-key",
|
||||
model: "gpt-4o",
|
||||
baseURL: customURL
|
||||
)
|
||||
|
||||
#expect(config.baseURL == customURL)
|
||||
let model = config.makeModel()
|
||||
#expect(model is OpenAILanguageModel)
|
||||
}
|
||||
|
||||
@Test("OpenAI with Responses API variant")
|
||||
func openAIResponsesVariant() {
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: "sk-test",
|
||||
model: "gpt-4o",
|
||||
variant: .responses
|
||||
)
|
||||
|
||||
#expect(config.openAIVariant == .responses)
|
||||
let model = config.makeModel()
|
||||
#expect(model is OpenAILanguageModel)
|
||||
}
|
||||
|
||||
@Test("OpenAI with dynamic key provider captures key by reference")
|
||||
func openAIDynamicKeyProvider() {
|
||||
nonisolated(unsafe) var currentKey = "sk-initial"
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKeyProvider: { currentKey },
|
||||
model: "gpt-4o"
|
||||
)
|
||||
|
||||
#expect(config.apiKey == "sk-initial")
|
||||
currentKey = "sk-rotated"
|
||||
#expect(config.apiKey == "sk-rotated")
|
||||
}
|
||||
|
||||
// MARK: - Anthropic Configuration
|
||||
|
||||
@Test("Anthropic configuration stores provider and model")
|
||||
func anthropicBasicConfiguration() {
|
||||
let config = ProviderConfiguration.anthropic(
|
||||
apiKey: "sk-ant-test-key",
|
||||
model: "claude-sonnet-4-20250514"
|
||||
)
|
||||
|
||||
#expect(config.provider == .anthropic)
|
||||
#expect(config.model == "claude-sonnet-4-20250514")
|
||||
#expect(config.apiKey == "sk-ant-test-key")
|
||||
#expect(config.hasValidAPIKey)
|
||||
}
|
||||
|
||||
@Test("Anthropic configuration produces a valid LanguageModel")
|
||||
func anthropicMakeModel() {
|
||||
let config = ProviderConfiguration.anthropic(
|
||||
apiKey: "sk-ant-test",
|
||||
model: "claude-sonnet-4-20250514"
|
||||
)
|
||||
|
||||
let model = config.makeModel()
|
||||
#expect(model is AnthropicLanguageModel)
|
||||
}
|
||||
|
||||
@Test("Anthropic with API version and betas")
|
||||
func anthropicWithVersionAndBetas() {
|
||||
let config = ProviderConfiguration.anthropic(
|
||||
apiKey: "sk-ant-test",
|
||||
model: "claude-sonnet-4-20250514",
|
||||
apiVersion: "2024-01-01",
|
||||
betas: ["computer-use"]
|
||||
)
|
||||
|
||||
#expect(config.apiVersion == "2024-01-01")
|
||||
#expect(config.betas == ["computer-use"])
|
||||
let model = config.makeModel()
|
||||
#expect(model is AnthropicLanguageModel)
|
||||
}
|
||||
|
||||
@Test("Anthropic with dynamic key provider captures key by reference")
|
||||
func anthropicDynamicKeyProvider() {
|
||||
nonisolated(unsafe) var currentKey = "sk-ant-initial"
|
||||
let config = ProviderConfiguration.anthropic(
|
||||
apiKeyProvider: { currentKey },
|
||||
model: "claude-sonnet-4-20250514"
|
||||
)
|
||||
|
||||
#expect(config.apiKey == "sk-ant-initial")
|
||||
currentKey = "sk-ant-rotated"
|
||||
#expect(config.apiKey == "sk-ant-rotated")
|
||||
}
|
||||
|
||||
// MARK: - Gemini Configuration
|
||||
|
||||
@Test("Gemini configuration stores provider and model")
|
||||
func geminiBasicConfiguration() {
|
||||
let config = ProviderConfiguration.gemini(
|
||||
apiKey: "AIzaSyTest123",
|
||||
model: "gemini-2.0-flash"
|
||||
)
|
||||
|
||||
#expect(config.provider == .gemini)
|
||||
#expect(config.model == "gemini-2.0-flash")
|
||||
#expect(config.apiKey == "AIzaSyTest123")
|
||||
#expect(config.hasValidAPIKey)
|
||||
}
|
||||
|
||||
@Test("Gemini configuration produces a valid LanguageModel")
|
||||
func geminiMakeModel() {
|
||||
let config = ProviderConfiguration.gemini(
|
||||
apiKey: "AIzaSyTest",
|
||||
model: "gemini-2.0-flash"
|
||||
)
|
||||
|
||||
let model = config.makeModel()
|
||||
#expect(model is GeminiLanguageModel)
|
||||
}
|
||||
|
||||
@Test("Gemini with custom API version")
|
||||
func geminiCustomVersion() {
|
||||
let config = ProviderConfiguration.gemini(
|
||||
apiKey: "AIzaSyTest",
|
||||
model: "gemini-2.0-flash",
|
||||
apiVersion: "v1"
|
||||
)
|
||||
|
||||
#expect(config.apiVersion == "v1")
|
||||
let model = config.makeModel()
|
||||
#expect(model is GeminiLanguageModel)
|
||||
}
|
||||
|
||||
@Test("Gemini with dynamic key provider captures key by reference")
|
||||
func geminiDynamicKeyProvider() {
|
||||
nonisolated(unsafe) var currentKey = "AIza-initial"
|
||||
let config = ProviderConfiguration.gemini(
|
||||
apiKeyProvider: { currentKey },
|
||||
model: "gemini-2.0-flash"
|
||||
)
|
||||
|
||||
#expect(config.apiKey == "AIza-initial")
|
||||
currentKey = "AIza-rotated"
|
||||
#expect(config.apiKey == "AIza-rotated")
|
||||
}
|
||||
|
||||
// MARK: - OpenAI-Compatible Configuration
|
||||
|
||||
@Test("OpenAI-compatible configuration with custom base URL")
|
||||
func openAICompatibleConfiguration() {
|
||||
let baseURL = URL(string: "https://api.together.xyz/v1/")!
|
||||
let config = ProviderConfiguration.openAICompatible(
|
||||
apiKey: "together-key",
|
||||
model: "meta-llama/Llama-3.1-70B",
|
||||
baseURL: baseURL
|
||||
)
|
||||
|
||||
#expect(config.provider == .openAICompatible)
|
||||
#expect(config.model == "meta-llama/Llama-3.1-70B")
|
||||
#expect(config.baseURL == baseURL)
|
||||
let model = config.makeModel()
|
||||
#expect(model is OpenAILanguageModel)
|
||||
}
|
||||
|
||||
@Test("OpenAI-compatible with dynamic key provider")
|
||||
func openAICompatibleDynamicKey() {
|
||||
let baseURL = URL(string: "http://localhost:1234/v1/")!
|
||||
nonisolated(unsafe) var currentKey = "local-key"
|
||||
let config = ProviderConfiguration.openAICompatible(
|
||||
apiKeyProvider: { currentKey },
|
||||
model: "local-model",
|
||||
baseURL: baseURL
|
||||
)
|
||||
|
||||
#expect(config.apiKey == "local-key")
|
||||
currentKey = "new-local-key"
|
||||
#expect(config.apiKey == "new-local-key")
|
||||
}
|
||||
|
||||
// MARK: - API Key Validation
|
||||
|
||||
@Test("Empty API key reports invalid")
|
||||
func emptyAPIKeyInvalid() {
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: "",
|
||||
model: "gpt-4o"
|
||||
)
|
||||
|
||||
#expect(!config.hasValidAPIKey)
|
||||
}
|
||||
|
||||
@Test("Whitespace-only API key reports invalid")
|
||||
func whitespaceAPIKeyInvalid() {
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: " \n\t ",
|
||||
model: "gpt-4o"
|
||||
)
|
||||
|
||||
#expect(!config.hasValidAPIKey)
|
||||
}
|
||||
|
||||
@Test("Non-empty API key reports valid")
|
||||
func nonEmptyAPIKeyValid() {
|
||||
let config = ProviderConfiguration.openAI(
|
||||
apiKey: "x",
|
||||
model: "gpt-4o"
|
||||
)
|
||||
|
||||
#expect(config.hasValidAPIKey)
|
||||
}
|
||||
|
||||
// MARK: - Environment Variable Configuration
|
||||
|
||||
@Test("fromEnvironment creates configuration for each provider")
|
||||
func fromEnvironmentCreatesConfig() {
|
||||
let openAI = ProviderConfiguration.fromEnvironment(
|
||||
provider: .openAI,
|
||||
environmentVariable: "SWIFTDAI_TEST_OPENAI_KEY",
|
||||
model: "gpt-4o"
|
||||
)
|
||||
#expect(openAI.provider == .openAI)
|
||||
#expect(openAI.model == "gpt-4o")
|
||||
|
||||
let anthropic = ProviderConfiguration.fromEnvironment(
|
||||
provider: .anthropic,
|
||||
environmentVariable: "SWIFTDAI_TEST_ANTHROPIC_KEY",
|
||||
model: "claude-sonnet-4-20250514"
|
||||
)
|
||||
#expect(anthropic.provider == .anthropic)
|
||||
|
||||
let gemini = ProviderConfiguration.fromEnvironment(
|
||||
provider: .gemini,
|
||||
environmentVariable: "SWIFTDAI_TEST_GEMINI_KEY",
|
||||
model: "gemini-2.0-flash"
|
||||
)
|
||||
#expect(gemini.provider == .gemini)
|
||||
}
|
||||
|
||||
@Test("fromEnvironment returns empty key when variable not set")
|
||||
func fromEnvironmentMissingVariable() {
|
||||
let config = ProviderConfiguration.fromEnvironment(
|
||||
provider: .openAI,
|
||||
environmentVariable: "NONEXISTENT_KEY_VAR_SWIFTDBAI_TEST",
|
||||
model: "gpt-4o"
|
||||
)
|
||||
|
||||
#expect(!config.hasValidAPIKey)
|
||||
#expect(config.apiKey == "")
|
||||
}
|
||||
|
||||
// MARK: - Provider Enum
|
||||
|
||||
@Test("Provider enum has all expected cases")
|
||||
func providerCases() {
|
||||
let cases = ProviderConfiguration.Provider.allCases
|
||||
#expect(cases.count == 6)
|
||||
#expect(cases.contains(.openAI))
|
||||
#expect(cases.contains(.anthropic))
|
||||
#expect(cases.contains(.gemini))
|
||||
#expect(cases.contains(.openAICompatible))
|
||||
#expect(cases.contains(.ollama))
|
||||
#expect(cases.contains(.llamaCpp))
|
||||
}
|
||||
|
||||
// MARK: - Cross-Provider Model Creation
|
||||
|
||||
@Test("All providers produce available models")
|
||||
func allProvidersCreateAvailableModels() {
|
||||
let configs: [ProviderConfiguration] = [
|
||||
.openAI(apiKey: "test", model: "gpt-4o"),
|
||||
.anthropic(apiKey: "test", model: "claude-sonnet-4-20250514"),
|
||||
.gemini(apiKey: "test", model: "gemini-2.0-flash"),
|
||||
.openAICompatible(
|
||||
apiKey: "test",
|
||||
model: "local",
|
||||
baseURL: URL(string: "http://localhost:8080/v1/")!
|
||||
),
|
||||
]
|
||||
|
||||
for config in configs {
|
||||
let model = config.makeModel()
|
||||
#expect(model.isAvailable, "Model for \(config.provider) should be available")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user