@_spi(Internal) import CliClient import Dependencies import Foundation import Logging import Models import MQTTNIO import XCTest final class CliClientTests: XCTestCase { override func invokeTest() { withDependencies { $0.cliClient = .liveValue $0.environment = .liveValue $0.environment.processInfo = { [:] } } operation: { super.invokeTest() } } func testParsingMQTTVersion() { @Dependency(\.cliClient) var cliClient let arguments = [ (MQTTClient.Version.v3_1_1, ["3", "3.1", "3.1.1", "00367894"]), (MQTTClient.Version.v5_0, ["5", "5.1", "5.1.1", "00000500012"]), (nil, ["0", "0.1", "0.1.1", "0000000001267894", "blob"]) ] for (version, strings) in arguments { for string in strings { XCTAssertEqual(cliClient.parseMqttClientVersion(string), version) } } XCTAssertEqual(MQTTClient.Version.parseOrDefault(string: nil), .v3_1_1) } func testLogLevelFromEnvironment() { @Dependency(\.cliClient) var cliClient let arguments = [ (Logger.Level.debug, EnvVars(appEnv: .staging, logLevel: nil)), (Logger.Level.debug, EnvVars(appEnv: .development, logLevel: nil)), (Logger.Level.info, EnvVars(appEnv: .production, logLevel: nil)), (Logger.Level.trace, EnvVars(appEnv: .testing, logLevel: nil)), (Logger.Level.info, EnvVars(appEnv: .staging, logLevel: .info)), (Logger.Level.trace, EnvVars(appEnv: .development, logLevel: .trace)), (Logger.Level.warning, EnvVars(appEnv: .production, logLevel: .warning)), (Logger.Level.debug, EnvVars(appEnv: .testing, logLevel: .debug)) ] for (expected, envVars) in arguments { XCTAssertEqual(expected, cliClient.logLevel(envVars)) } } func testMakeEnvVars() async throws { @Dependency(\.cliClient) var cliClient @Dependency(\.environment) var environment let arguments = [ ( CliClient.EnvVarsRequest(envFilePath: nil, logger: nil, version: nil), EnvVars() ), ( CliClient.EnvVarsRequest(envFilePath: nil, logger: nil, version: "3"), EnvVars(version: "3") ), ( CliClient.EnvVarsRequest( envFilePath: "test.env", // Needs to be a bundled resource. logger: nil, version: nil ), EnvVars.test ), ( CliClient.EnvVarsRequest( envFilePath: "test-env.json", // Needs to be a bundled resource. logger: nil, version: nil ), EnvVars.test ) ] for (request, expectedEnvVars) in arguments { var request = request if let file = request.envFilePath { request = .init( envFilePath: cleanFilePath(file), logger: request.logger, version: request.mqttClientVersion ) } let result = try await cliClient.makeEnvVars(request) XCTAssertEqual(result, expectedEnvVars) } } 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() { let arguments = [ (EnvironmentDependency.FileType.dotEnv(path: "test.env"), "test.env"), (EnvironmentDependency.FileType.json(path: "test.json"), "test.json"), (nil, "test"), (nil, "") ] for (expected, file) in arguments { XCTAssertEqual(EnvironmentDependency.FileType(path: file), expected) } } func testEnvironmentLiveValueProcessInfo() { let environment = EnvironmentDependency.liveValue XCTAssertEqual(environment.processInfo(), ProcessInfo.processInfo.environment) } func testMakeClient() throws { @Dependency(\.cliClient) var cliClient let envVars = EnvVars.test let client = try cliClient.makeClient(.init( environment: envVars, eventLoopGroup: .init(numberOfThreads: 1), logger: nil )) XCTAssertEqual(client.host, envVars.host) XCTAssertEqual(client.port, Int(envVars.port!)) XCTAssertEqual(client.identifier, envVars.identifier) XCTAssertEqual(client.configuration.version, .v5_0) XCTAssertEqual(client.configuration.userName, envVars.userName) XCTAssertEqual(client.configuration.password, envVars.password) try client.syncShutdownGracefully() } } // - MARK: Helper private func cleanFilePath(_ path: String) -> String { #if os(Linux) return "Tests/CliClientTests/\(path)" #else let split = path.split(separator: ".") let fileName = split.first! let ext = split.last! let url = Bundle.module.url(forResource: String(fileName), withExtension: String(ext))!.absoluteString let cleaned = url.split(separator: "file://").last! return String(cleaned) #endif } extension EnvVars { static let test = EnvVars( appEnv: .testing, host: "test.mqtt", port: "1234", identifier: "testing-mqtt", userName: "test-user", password: "super-secret", logLevel: .debug, version: "5.0" ) } struct ThrowingDecoder: Coderable { func encode(_ instance: T) throws -> Data where T: Encodable { try JSONEncoder().encode(instance) } func decode(_ type: T.Type, from data: Data) throws -> T where T: Decodable { throw DecodeError() } } struct ThrowingEncoder: Coderable { func encode(_ instance: T) throws -> Data where T: Encodable { throw EncodeError() } func decode(_ type: T.Type, from data: Data) throws -> T where T: Decodable { try JSONDecoder().decode(T.self, from: data) } } struct DecodeError: Error {} struct EncodeError: Error {}