feat: More cli client tests and documentation.
All checks were successful
CI / Run Tests (push) Successful in 4m57s
All checks were successful
CI / Run Tests (push) Successful in 4m57s
This commit is contained in:
@@ -34,12 +34,23 @@ public struct CliClient {
|
|||||||
|
|
||||||
/// Represents the parameters needed to create an `MQTTClient`.
|
/// Represents the parameters needed to create an `MQTTClient`.
|
||||||
///
|
///
|
||||||
///
|
|
||||||
public struct ClientRequest: Sendable {
|
public struct ClientRequest: Sendable {
|
||||||
|
|
||||||
|
/// The environment variables used to create the client.
|
||||||
public let environment: EnvVars
|
public let environment: EnvVars
|
||||||
|
|
||||||
|
/// The event loop group for the client.
|
||||||
public let eventLoopGroup: MultiThreadedEventLoopGroup
|
public let eventLoopGroup: MultiThreadedEventLoopGroup
|
||||||
|
|
||||||
|
/// A logger to use with the client.
|
||||||
public let logger: Logger?
|
public let logger: Logger?
|
||||||
|
|
||||||
|
/// Create a new client request.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - environment: The environment variables to use.
|
||||||
|
/// - eventLoopGroup: The event loop group to use on the client.
|
||||||
|
/// - logger: An optional logger to use on the client.
|
||||||
public init(
|
public init(
|
||||||
environment: EnvVars,
|
environment: EnvVars,
|
||||||
eventLoopGroup: MultiThreadedEventLoopGroup,
|
eventLoopGroup: MultiThreadedEventLoopGroup,
|
||||||
@@ -122,11 +133,10 @@ extension EnvVars {
|
|||||||
@Dependency(\.environment) var environment
|
@Dependency(\.environment) var environment
|
||||||
|
|
||||||
let defaultEnvVars = EnvVars()
|
let defaultEnvVars = EnvVars()
|
||||||
let encoder = environment.jsonEncoder()
|
let coders = environment.coders()
|
||||||
let decoder = environment.jsonDecoder()
|
|
||||||
|
|
||||||
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
|
let defaultEnvDict = (try? coders.encode(defaultEnvVars))
|
||||||
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
.flatMap { try? coders.decode([String: String].self, from: $0) }
|
||||||
?? [:]
|
?? [:]
|
||||||
|
|
||||||
let dotEnvDict = try await environment.dotEnvDict(path: dotEnvFile)
|
let dotEnvDict = try await environment.dotEnvDict(path: dotEnvFile)
|
||||||
@@ -135,7 +145,7 @@ extension EnvVars {
|
|||||||
.merging(dotEnvDict, uniquingKeysWith: { $1 })
|
.merging(dotEnvDict, uniquingKeysWith: { $1 })
|
||||||
|
|
||||||
var envVars = (try? JSONSerialization.data(withJSONObject: envVarsDict))
|
var envVars = (try? JSONSerialization.data(withJSONObject: envVarsDict))
|
||||||
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
.flatMap { try? coders.decode(EnvVars.self, from: $0) }
|
||||||
?? defaultEnvVars
|
?? defaultEnvVars
|
||||||
|
|
||||||
if let version {
|
if let version {
|
||||||
@@ -162,7 +172,7 @@ public extension MQTTClient {
|
|||||||
eventLoopGroupProvider: .shared(eventLoopGroup),
|
eventLoopGroupProvider: .shared(eventLoopGroup),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
configuration: .init(
|
configuration: .init(
|
||||||
version: .parseOrDefualt(string: envVars.version),
|
version: .parseOrDefault(string: envVars.version),
|
||||||
disablePing: false,
|
disablePing: false,
|
||||||
userName: envVars.userName,
|
userName: envVars.userName,
|
||||||
password: envVars.password
|
password: envVars.password
|
||||||
@@ -171,10 +181,11 @@ public extension MQTTClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MQTTClient.Version {
|
@_spi(Internal)
|
||||||
|
public extension MQTTClient.Version {
|
||||||
static let `default` = Self.v3_1_1
|
static let `default` = Self.v3_1_1
|
||||||
|
|
||||||
static func parseOrDefualt(string: String?) -> Self {
|
static func parseOrDefault(string: String?) -> Self {
|
||||||
guard let string, let value = Self(string: string) else {
|
guard let string, let value = Self(string: string) else {
|
||||||
return .default
|
return .default
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,7 @@ public extension DependencyValues {
|
|||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct EnvironmentDependency: Sendable {
|
public struct EnvironmentDependency: Sendable {
|
||||||
|
|
||||||
/// A json decoder to use to decode files and environment variables.
|
public var coders: @Sendable () -> any Coderable = { JSONCoders() }
|
||||||
public var jsonDecoder: @Sendable () -> JSONDecoder = { JSONDecoder() }
|
|
||||||
|
|
||||||
/// A json encoder to use to encode files and environment variables.
|
|
||||||
public var jsonEncoder: @Sendable () -> JSONEncoder = { JSONEncoder() }
|
|
||||||
|
|
||||||
/// Load the variables based on the request.
|
/// Load the variables based on the request.
|
||||||
public var load: @Sendable (FileType) async throws -> [String: String] = { _ in [:] }
|
public var load: @Sendable (FileType) async throws -> [String: String] = { _ in [:] }
|
||||||
@@ -61,6 +57,8 @@ public struct EnvironmentDependency: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DecodeError: Error {}
|
||||||
|
|
||||||
@_spi(Internal)
|
@_spi(Internal)
|
||||||
extension EnvironmentDependency: DependencyKey {
|
extension EnvironmentDependency: DependencyKey {
|
||||||
|
|
||||||
@@ -71,8 +69,7 @@ extension EnvironmentDependency: DependencyKey {
|
|||||||
encoder: JSONEncoder = .init()
|
encoder: JSONEncoder = .init()
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self(
|
Self(
|
||||||
jsonDecoder: { decoder },
|
coders: { JSONCoders(decoder: decoder, encoder: encoder) },
|
||||||
jsonEncoder: { encoder },
|
|
||||||
load: { file in
|
load: { file in
|
||||||
switch file {
|
switch file {
|
||||||
case let .dotEnv(path: path):
|
case let .dotEnv(path: path):
|
||||||
@@ -95,6 +92,39 @@ extension EnvironmentDependency: DependencyKey {
|
|||||||
public static let liveValue: EnvironmentDependency = .live()
|
public static let liveValue: EnvironmentDependency = .live()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type that encode and decode values.
|
||||||
|
///
|
||||||
|
/// This is really just here to override tests with coders that will throw an error,
|
||||||
|
/// instead of encoding or decoding data.
|
||||||
|
///
|
||||||
|
@_spi(Internal)
|
||||||
|
public protocol Coderable {
|
||||||
|
func encode<T: Encodable>(_ instance: T) throws -> Data
|
||||||
|
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JSONCoders: Coderable {
|
||||||
|
|
||||||
|
let decoder: JSONDecoder
|
||||||
|
let encoder: JSONEncoder
|
||||||
|
|
||||||
|
init(
|
||||||
|
decoder: JSONDecoder = .init(),
|
||||||
|
encoder: JSONEncoder = .init()
|
||||||
|
) {
|
||||||
|
self.decoder = decoder
|
||||||
|
self.encoder = encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode<T>(_ instance: T) throws -> Data where T: Encodable {
|
||||||
|
try encoder.encode(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable {
|
||||||
|
try decoder.decode(T.self, from: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func url(for path: String) -> URL {
|
private func url(for path: String) -> URL {
|
||||||
#if os(Linux)
|
#if os(Linux)
|
||||||
return URL(fileURLWithPath: path)
|
return URL(fileURLWithPath: path)
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ final class CliClientTests: XCTestCase {
|
|||||||
XCTAssertEqual(cliClient.parseMqttClientVersion(string), version)
|
XCTAssertEqual(cliClient.parseMqttClientVersion(string), version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(MQTTClient.Version.parseOrDefault(string: nil), .v3_1_1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLogLevelFromEnvironment() {
|
func testLogLevelFromEnvironment() {
|
||||||
@@ -98,6 +100,26 @@ final class CliClientTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMakeEnvVarsWithFailingDecoder() async throws {
|
||||||
|
try await withDependencies {
|
||||||
|
$0.environment.coders = { ThrowingDecoder() }
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.cliClient) var cliClient
|
||||||
|
let envVars = try await cliClient.makeEnvVars(.init())
|
||||||
|
XCTAssertEqual(envVars, EnvVars())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMakeEnvVarsWithFailingEncoder() async throws {
|
||||||
|
try await withDependencies {
|
||||||
|
$0.environment.coders = { ThrowingEncoder() }
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.cliClient) var cliClient
|
||||||
|
let envVars = try await cliClient.makeEnvVars(.init())
|
||||||
|
XCTAssertEqual(envVars, EnvVars())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testFileType() {
|
func testFileType() {
|
||||||
let arguments = [
|
let arguments = [
|
||||||
(EnvironmentDependency.FileType.dotEnv(path: "test.env"), "test.env"),
|
(EnvironmentDependency.FileType.dotEnv(path: "test.env"), "test.env"),
|
||||||
@@ -139,12 +161,16 @@ final class CliClientTests: XCTestCase {
|
|||||||
// - MARK: Helper
|
// - MARK: Helper
|
||||||
|
|
||||||
private func cleanFilePath(_ path: String) -> String {
|
private func cleanFilePath(_ path: String) -> String {
|
||||||
|
#if os(Linux)
|
||||||
|
return "Tests/CliClientTests/\(path)"
|
||||||
|
#else
|
||||||
let split = path.split(separator: ".")
|
let split = path.split(separator: ".")
|
||||||
let fileName = split.first!
|
let fileName = split.first!
|
||||||
let ext = split.last!
|
let ext = split.last!
|
||||||
let url = Bundle.module.url(forResource: String(fileName), withExtension: String(ext))!.absoluteString
|
let url = Bundle.module.url(forResource: String(fileName), withExtension: String(ext))!.absoluteString
|
||||||
let cleaned = url.split(separator: "file://").last!
|
let cleaned = url.split(separator: "file://").last!
|
||||||
return String(cleaned)
|
return String(cleaned)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EnvVars {
|
extension EnvVars {
|
||||||
@@ -159,3 +185,28 @@ extension EnvVars {
|
|||||||
version: "5.0"
|
version: "5.0"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ThrowingDecoder: Coderable {
|
||||||
|
|
||||||
|
func encode<T>(_ instance: T) throws -> Data where T: Encodable {
|
||||||
|
try JSONEncoder().encode(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable {
|
||||||
|
throw DecodeError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThrowingEncoder: Coderable {
|
||||||
|
|
||||||
|
func encode<T>(_ instance: T) throws -> Data where T: Encodable {
|
||||||
|
throw EncodeError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable {
|
||||||
|
try JSONDecoder().decode(T.self, from: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DecodeError: Error {}
|
||||||
|
struct EncodeError: Error {}
|
||||||
|
|||||||
Reference in New Issue
Block a user