Files
async-http-client/Tests/AsyncHTTPClientTests/SwiftConfigurationTests.swift
T
hamzahrmalikandGitHub 986dc47c11 Add swift-configuration support (#878)
Users should be able to use swift-configuration to create the http
client configuration object

Changes
- duplicate package.swift to create a separate version for 6.0 and 6.1, 
since Configuration is 6.2+
- add helper to create http client configuration using ConfigReader
2026-02-05 12:12:14 +01:00

280 lines
11 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2026 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if compiler(>=6.2)
import Configuration
import Foundation
import NIOCore
import Testing
@testable import AsyncHTTPClient
struct HTTPClientConfigurationPropsTests {
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func allPropertiesAreSetFromConfig() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["localhost:127.0.0.1", "example.com:192.168.1.1"]), isSecret: false),
"redirect.mode": "follow",
"redirect.maxRedirects": 10,
"redirect.allowCycles": true,
"timeout.connectionMs": 5000,
"timeout.readMs": 30000,
"timeout.writeMs": 15000,
"connectionPool.idleTimeoutMs": 120_000,
"connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit": 16,
"connectionPool.retryConnectionEstablishment": false,
"connectionPool.preWarmedHTTP1ConnectionCount": 5,
"httpVersion": "http1Only",
"maximumUsesPerConnection": 100,
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.dnsOverride["localhost"] == "127.0.0.1")
#expect(config.dnsOverride["example.com"] == "192.168.1.1")
switch config.redirectConfiguration.mode {
case .follow(let max, let allowCycles):
#expect(max == 10)
#expect(allowCycles)
case .disallow:
Issue.record("Unexpected value")
}
#expect(config.timeout.connect == .milliseconds(5000))
#expect(config.timeout.read == .milliseconds(30000))
#expect(config.timeout.write == .milliseconds(15000))
#expect(config.connectionPool.idleTimeout == .milliseconds(120000))
#expect(config.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit == 16)
#expect(config.connectionPool.retryConnectionEstablishment == false)
#expect(config.connectionPool.preWarmedHTTP1ConnectionCount == 5)
#expect(config.httpVersion == .http1Only)
#expect(config.maximumUsesPerConnection == 100)
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func defaultsAreUsedWhenConfigIsEmpty() throws {
let testProvider = InMemoryProvider(values: [:])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.dnsOverride.isEmpty)
#expect(config.timeout.connect == nil)
#expect(config.timeout.read == nil)
#expect(config.timeout.write == nil)
#expect(config.connectionPool.idleTimeout == .seconds(60))
#expect(config.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit == 8)
#expect(config.connectionPool.retryConnectionEstablishment == true)
#expect(config.connectionPool.preWarmedHTTP1ConnectionCount == 0)
#expect(config.httpVersion == .automatic)
#expect(config.maximumUsesPerConnection == nil)
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func redirectConfigurationDisallow() throws {
let testProvider = InMemoryProvider(values: ["redirect.mode": "disallow"])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
switch config.redirectConfiguration.mode {
case .disallow:
break
case .follow:
Issue.record("Unexpected value")
}
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func redirectConfigurationInvalidModeThrowsError() throws {
let testProvider = InMemoryProvider(values: ["redirect.mode": "invalid"])
let configReader = ConfigReader(provider: testProvider)
#expect(throws: HTTPClientError.invalidRedirectConfiguration) {
_ = try HTTPClient.Configuration(configReader: configReader)
}
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func httpVersionAutomatic() throws {
let testProvider = InMemoryProvider(values: ["httpVersion": "automatic"])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.httpVersion == .automatic)
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func httpVersionInvalidThrowsError() throws {
let testProvider = InMemoryProvider(values: ["httpVersion": "http3"])
let configReader = ConfigReader(provider: testProvider)
#expect(throws: HTTPClientError.invalidHTTPVersionConfiguration) {
try HTTPClient.Configuration(configReader: configReader)
}
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func dnsOverridesWithIPv6() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["localhost:::1", "example.com:2001:db8::1"]), isSecret: false)
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.dnsOverride["localhost"] == "::1")
#expect(config.dnsOverride["example.com"] == "2001:db8::1")
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func dnsOverridesWithInvalidFormat() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["invalidentry", "localhost:127.0.0.1"]), isSecret: false)
])
let configReader = ConfigReader(provider: testProvider)
#expect(throws: HTTPClientError.invalidDNSOverridesConfiguration) {
_ = try HTTPClient.Configuration(configReader: configReader)
}
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func dnsOverridesWithBlankValue() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["localhost:"]), isSecret: false)
])
let configReader = ConfigReader(provider: testProvider)
#expect(throws: HTTPClientError.invalidDNSOverridesConfiguration) {
_ = try HTTPClient.Configuration(configReader: configReader)
}
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func dnsOverridesWithBlankKey() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray([":127.0.0.1"]), isSecret: false)
])
let configReader = ConfigReader(provider: testProvider)
#expect(throws: HTTPClientError.invalidDNSOverridesConfiguration) {
_ = try HTTPClient.Configuration(configReader: configReader)
}
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func dnsOverridesWithSpaces() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["test.com: 127.0.0.1"]), isSecret: false)
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.dnsOverride == ["test.com": "127.0.0.1"])
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func timeoutConfigurationPartial() throws {
let testProvider = InMemoryProvider(values: [
"timeout.connectionMs": 1000,
"timeout.readMs": 2000,
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.timeout.connect == .milliseconds(1000))
#expect(config.timeout.read == .milliseconds(2000))
#expect(config.timeout.write == nil)
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func connectionPoolConfigurationPartial() throws {
let testProvider = InMemoryProvider(values: [
"connectionPool.idleTimeoutMs": 90000,
"connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit": 12,
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.connectionPool.idleTimeout == .milliseconds(90000))
#expect(config.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit == 12)
// These should use defaults
#expect(config.connectionPool.retryConnectionEstablishment)
#expect(config.connectionPool.preWarmedHTTP1ConnectionCount == 0)
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func redirectConfigurationWithDefaults() throws {
let testProvider = InMemoryProvider(values: [
"redirect.mode": "follow"
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.redirectConfiguration.mode == .follow(max: 5, allowCycles: false))
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func redirectConfigurationCustomValues() throws {
let testProvider = InMemoryProvider(values: [
"redirect.mode": "follow",
"redirect.maxRedirects": 3,
"redirect.allowCycles": true,
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.redirectConfiguration.mode == .follow(max: 3, allowCycles: true))
}
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func emptyDnsOverridesArray() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": "[]"
])
let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)
#expect(config.dnsOverride.isEmpty)
}
}
#endif