import CodexBarCore import Foundation import Testing @testable import CodexBar struct AzureOpenAIUsageFetcherTests { private func makeContext(environment: [String: String]) -> ProviderFetchContext { let browserDetection = BrowserDetection(cacheTTL: 0) return ProviderFetchContext( runtime: .app, sourceMode: .auto, includeCredits: false, webTimeout: 1, webDebugDumpHTML: false, verbose: false, env: environment, settings: nil, fetcher: UsageFetcher(environment: environment), claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), browserDetection: browserDetection) } @Test func `settings reader trims env vars and normalizes endpoint`() { let environment = [ AzureOpenAISettingsReader.apiKeyEnvironmentKey: " 'azure-key' ", AzureOpenAISettingsReader.endpointEnvironmentKey: "my-resource.openai.azure.com", AzureOpenAISettingsReader.deploymentNameEnvironmentKey: " \"chat-deployment\" ", ] #expect(AzureOpenAISettingsReader.apiKey(environment: environment) == "azure-key") #expect( AzureOpenAISettingsReader.endpoint(environment: environment)?.absoluteString == "https://my-resource.openai.azure.com") #expect(AzureOpenAISettingsReader.deploymentName(environment: environment) == "chat-deployment") #expect(AzureOpenAISettingsReader.apiVersion(environment: [:]) == AzureOpenAISettingsReader.defaultAPIVersion) } @Test func `missing deployment config returns precise provider error`() async { let descriptor = ProviderDescriptorRegistry.descriptor(for: .azureopenai) let outcome = await descriptor.fetchPlan.fetchOutcome( context: self.makeContext(environment: [ AzureOpenAISettingsReader.apiKeyEnvironmentKey: "azure-key", AzureOpenAISettingsReader.endpointEnvironmentKey: "https://example-resource.openai.azure.com", ]), provider: .azureopenai) guard case let .failure(error) = outcome.result else { Issue.record("Expected missing deployment to fail") return } #expect(error as? AzureOpenAIUsageError == .missingDeploymentName) #expect(error.localizedDescription.contains("deployment not configured")) #expect(outcome.attempts.map(\.wasAvailable) == [true]) } @Test func `invalid endpoint returns precise provider error before fetch`() async { let descriptor = ProviderDescriptorRegistry.descriptor(for: .azureopenai) let outcome = await descriptor.fetchPlan.fetchOutcome( context: self.makeContext(environment: [ AzureOpenAISettingsReader.apiKeyEnvironmentKey: "AZURE_CANARY_KEY", AzureOpenAISettingsReader.endpointEnvironmentKey: "http://127.0.0.1:31337", AzureOpenAISettingsReader.deploymentNameEnvironmentKey: "canary-deployment", ]), provider: .azureopenai) guard case let .failure(error) = outcome.result else { Issue.record("Expected invalid endpoint override to fail") return } #expect(error as? AzureOpenAISettingsError == .invalidEndpointOverride( AzureOpenAISettingsReader.endpointEnvironmentKey)) #expect(error.localizedDescription.contains("HTTPS endpoint")) #expect(outcome.attempts.map(\.wasAvailable) == [true]) } @Test func `fetcher validates deployment with chat completions request`() async throws { let endpoint = try #require(URL(string: "https://example-resource.openai.azure.com")) let updatedAt = Date(timeIntervalSince1970: 1_800_000_000) let transport = ProviderHTTPTransportStub { request in #expect(request.httpMethod == "POST") #expect(request.url?.path == "/openai/deployments/chat-prod/chat/completions") #expect(request.url?.query == "api-version=2024-10-21") #expect(request.value(forHTTPHeaderField: "api-key") == "azure-key") #expect(request.value(forHTTPHeaderField: "Accept") == "application/json") #expect(request.value(forHTTPHeaderField: "Content-Type") == "application/json") let body = try #require(request.httpBody) let json = try #require(JSONSerialization.jsonObject(with: body) as? [String: Any]) #expect(json["max_tokens"] as? Int == 1) #expect(json["temperature"] == nil) let messages = try #require(json["messages"] as? [[String: String]]) #expect(messages.first?["content"] == "ping") let response = try HTTPURLResponse( url: #require(request.url), statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "application/json"])! return (Data(#"{"id":"cmpl-1","model":"gpt-4o-mini"}"#.utf8), response) } let snapshot = try await AzureOpenAIUsageFetcher.fetchUsage( apiKey: "azure-key", endpoint: endpoint, deploymentName: "chat-prod", transport: transport, updatedAt: updatedAt) #expect(snapshot.endpointHost == "example-resource.openai.azure.com") #expect(snapshot.deploymentName == "chat-prod") #expect(snapshot.model == "gpt-4o-mini") #expect(snapshot.apiVersion == AzureOpenAISettingsReader.defaultAPIVersion) #expect(snapshot.updatedAt == updatedAt) let usage = snapshot.toUsageSnapshot() #expect(usage.identity?.providerID == .azureopenai) #expect(usage.identity?.accountOrganization == "example-resource.openai.azure.com") #expect(usage.identity?.loginMethod == "Deployment: chat-prod") #expect(usage.primary?.resetDescription == "Deployment: chat-prod · Model: gpt-4o-mini") } @Test func `chat completions URL preserves endpoint path and deployment escaping`() throws { let endpoint = try #require(URL(string: "https://proxy.example.com/base")) let url = try AzureOpenAIUsageFetcher._chatCompletionsURLForTesting( endpoint: endpoint, deploymentName: "chat prod", apiVersion: "2024-10-21") #expect( url.absoluteString == "https://proxy.example.com/base/openai/deployments/chat%20prod/chat/completions?api-version=2024-10-21") } @Test func `chat completions URL does not duplicate openai endpoint suffix`() throws { let endpoint = try #require(URL(string: "https://proxy.example.com/base/openai")) let url = try AzureOpenAIUsageFetcher._chatCompletionsURLForTesting( endpoint: endpoint, deploymentName: "chat-prod", apiVersion: "2024-10-21") #expect( url.absoluteString == "https://proxy.example.com/base/openai/deployments/chat-prod/chat/completions?api-version=2024-10-21") } @Test func `v1 API validates with OpenAI compatible path and model field`() async throws { let endpoint = try #require(URL(string: "https://example-resource.openai.azure.com")) let transport = ProviderHTTPTransportStub { request in #expect(request.httpMethod == "POST") #expect( request.url?.absoluteString == "https://example-resource.openai.azure.com/openai/v1/chat/completions") #expect(request.value(forHTTPHeaderField: "api-key") == "azure-key") let body = try #require(request.httpBody) let json = try #require(JSONSerialization.jsonObject(with: body) as? [String: Any]) #expect(json["model"] as? String == "chat-prod") #expect(json["max_completion_tokens"] as? Int == 1) #expect(json["max_tokens"] == nil) #expect(json["temperature"] == nil) let response = try HTTPURLResponse( url: #require(request.url), statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "application/json"])! return (Data(#"{"id":"cmpl-1","model":"gpt-4o-mini"}"#.utf8), response) } let snapshot = try await AzureOpenAIUsageFetcher.fetchUsage( apiKey: "azure-key", endpoint: endpoint, deploymentName: "chat-prod", apiVersion: "v1", transport: transport) #expect(snapshot.apiVersion == "v1") #expect(snapshot.deploymentName == "chat-prod") #expect(snapshot.model == "gpt-4o-mini") } @Test func `v1 API accepts documented openai v1 base URL`() throws { let endpoint = try #require(URL(string: "https://example-resource.openai.azure.com/openai/v1")) let url = try AzureOpenAIUsageFetcher._chatCompletionsURLForTesting( endpoint: endpoint, deploymentName: "chat-prod", apiVersion: "v1") #expect( url.absoluteString == "https://example-resource.openai.azure.com/openai/v1/chat/completions") } } @MainActor struct AzureOpenAIProviderAvailabilityTests { @Test func `configured invalid endpoint remains visible for actionable error`() throws { let suite = "AzureOpenAIProviderAvailabilityTests-\(UUID().uuidString)" let defaults = try #require(UserDefaults(suiteName: suite)) defaults.removePersistentDomain(forName: suite) defer { defaults.removePersistentDomain(forName: suite) } let settings = SettingsStore( userDefaults: defaults, configStore: testConfigStore(suiteName: suite), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.azureOpenAIAPIKey = "AZURE_CANARY_KEY" settings.azureOpenAIEndpoint = "http://127.0.0.1:31337" settings.azureOpenAIDeploymentName = "canary-deployment" let environment = ProviderRegistry.makeEnvironment( base: [:], provider: .azureopenai, settings: settings, tokenOverride: nil) let context = ProviderAvailabilityContext( provider: .azureopenai, settings: settings, environment: environment) #expect(AzureOpenAISettingsReader.endpoint(environment: environment) == nil) #expect(AzureOpenAIProviderImplementation().isAvailable(context: context)) } } @MainActor struct AzureOpenAIMenuDescriptorTests { @Test func `azure openai deployment detail appears in menu`() throws { let suite = "AzureOpenAIMenuDescriptorTests-menu" let defaults = try #require(UserDefaults(suiteName: suite)) defaults.removePersistentDomain(forName: suite) let settings = SettingsStore( userDefaults: defaults, configStore: testConfigStore(suiteName: suite), zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) settings.statusChecksEnabled = false let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let snapshot = AzureOpenAIUsageSnapshot( endpointHost: "example-resource.openai.azure.com", deploymentName: "chat-prod", model: "gpt-4o-mini", apiVersion: "2024-10-21", updatedAt: Date(timeIntervalSince1970: 1_800_000_000)) store._setSnapshotForTesting(snapshot.toUsageSnapshot(), provider: .azureopenai) let descriptor = MenuDescriptor.build( provider: .azureopenai, store: store, settings: settings, account: AccountInfo(email: nil, plan: nil), updateReady: false, includeContextualActions: false) let lines = descriptor.sections .flatMap(\.entries) .compactMap { entry -> String? in guard case let .text(text, _) = entry else { return nil } return text } #expect(lines.contains("Azure OpenAI")) #expect(lines.contains("Deployment: chat-prod · Model: gpt-4o-mini")) } }