diff --git a/.gitignore b/.gitignore
index bb9d983..2836264 100755
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,7 @@
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
-.dewPoint-env
+.dewPoint-env*
.topics
mqtt_password.txt
.env
diff --git a/.swiftlint.yml b/.swiftlint.yml
index e2ccc42..8df1f05 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -1,2 +1,9 @@
disabled_rules:
- closing_brace
+ - fuction_body_length
+
+included:
+ - Sources
+ - Tests
+
+ignore_multiline_statement_conditions: true
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/MQTTConnectionService.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/MQTTConnectionService.xcscheme
deleted file mode 100644
index 45f5e1a..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/MQTTConnectionService.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/MQTTManager.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/MQTTManager.xcscheme
deleted file mode 100644
index a9416cb..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/MQTTManager.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Models.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Models.xcscheme
deleted file mode 100755
index 4435fa7..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Models.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SensorsService.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SensorsService.xcscheme
deleted file mode 100644
index f4e1101..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/SensorsService.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/dewPoint-controller-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/dewPoint-controller-Package.xcscheme
index 4ff0e6e..c67fbf9 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/dewPoint-controller-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/dewPoint-controller-Package.xcscheme
@@ -7,6 +7,20 @@
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Package.swift b/Package.swift
index 373e6ec..ae08c9a 100755
--- a/Package.swift
+++ b/Package.swift
@@ -48,7 +48,8 @@ let package = Package(
"CliClient"
],
resources: [
- .copy("test.env")
+ .copy("test.env"),
+ .copy("test-env.json")
]
),
.executableTarget(
diff --git a/Sources/CliClient/CliClient.swift b/Sources/CliClient/CliClient.swift
index 5b165fa..50f1b03 100644
--- a/Sources/CliClient/CliClient.swift
+++ b/Sources/CliClient/CliClient.swift
@@ -67,6 +67,7 @@ public struct CliClient {
self.logger = logger
self.mqttClientVersion = mqttClientVersion
}
+
}
}
@@ -121,8 +122,8 @@ extension EnvVars {
@Dependency(\.environment) var environment
let defaultEnvVars = EnvVars()
- let encoder = JSONEncoder()
- let decoder = JSONDecoder()
+ let encoder = environment.jsonEncoder()
+ let decoder = environment.jsonDecoder()
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
.flatMap { try? decoder.decode([String: String].self, from: $0) }
diff --git a/Sources/CliClient/EnvironmentDependency.swift b/Sources/CliClient/EnvironmentDependency.swift
index 607d4b8..e985668 100644
--- a/Sources/CliClient/EnvironmentDependency.swift
+++ b/Sources/CliClient/EnvironmentDependency.swift
@@ -6,6 +6,11 @@ import Models
@_spi(Internal)
public extension DependencyValues {
+
+ /// A dependecy responsible for loding environment variables.
+ ///
+ /// This is just used internally of this module, but is useful to
+ /// override for testing purposes, so import using `@_spi(Internal)`.
var environment: EnvironmentDependency {
get { self[EnvironmentDependency.self] }
set { self[EnvironmentDependency.self] = newValue }
@@ -19,16 +24,27 @@ public extension DependencyValues {
@DependencyClient
public struct EnvironmentDependency: Sendable {
+ /// A json decoder to use to decode files and environment variables.
+ 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.
public var load: @Sendable (FileType) async throws -> [String: String] = { _ in [:] }
+ /// Load the environment variables based on the current process environment.
+ ///
+ /// You can override this to use an empty environment, which is useful for testing purposes.
public var processInfo: @Sendable () -> [String: String] = { [:] }
+ /// Represents the types of files that can be loaded and decoded into
+ /// the environment.
public enum FileType: Equatable {
case dotEnv(path: String)
case json(path: String)
- init?(path: String) {
+ public init?(path: String) {
let strings = path.split(separator: ".")
guard let ext = strings.last else {
return nil
@@ -50,22 +66,39 @@ extension EnvironmentDependency: DependencyKey {
public static let testValue: EnvironmentDependency = Self()
- public static let liveValue: EnvironmentDependency = Self(
- load: { file in
- switch file {
- case let .dotEnv(path: path):
- let file = try DotEnv.read(path: path)
- return file.lines.reduce(into: [String: String]()) { partialResult, line in
- partialResult[line.key] = line.value
+ public static func live(
+ decoder: JSONDecoder = .init(),
+ encoder: JSONEncoder = .init()
+ ) -> Self {
+ Self(
+ jsonDecoder: { decoder },
+ jsonEncoder: { encoder },
+ load: { file in
+ switch file {
+ case let .dotEnv(path: path):
+ let file = try DotEnv.read(path: path)
+ return file.lines.reduce(into: [String: String]()) { partialResult, line in
+ partialResult[line.key] = line.value
+ }
+ case let .json(path: path):
+ let url = url(for: path)
+ return try decoder.decode(
+ [String: String].self,
+ from: Data(contentsOf: url)
+ )
}
- case let .json(path: path):
- let url = URL(filePath: path)
- return try JSONDecoder().decode(
- [String: String].self,
- from: Data(contentsOf: url)
- )
- }
- },
- processInfo: { ProcessInfo.processInfo.environment }
- )
+ },
+ processInfo: { ProcessInfo.processInfo.environment }
+ )
+ }
+
+ public static let liveValue: EnvironmentDependency = .live()
+}
+
+private func url(for path: String) -> URL {
+ #if os(Linux)
+ return URL(fileURLWithPath: path)
+ #else
+ return URL(filePath: path)
+ #endif
}
diff --git a/Tests/CliClientTests/CliClientTests.swift b/Tests/CliClientTests/CliClientTests.swift
index c76ebe8..93cf3cf 100644
--- a/Tests/CliClientTests/CliClientTests.swift
+++ b/Tests/CliClientTests/CliClientTests.swift
@@ -4,96 +4,139 @@ import Foundation
import Logging
import Models
import MQTTNIO
-import Testing
+import XCTest
-@Test
-func checkTesting() {
- #expect(Bool(true))
-}
+final class CliClientTests: XCTestCase {
-@Test(
- 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"])
- ]
-)
-func checkParseMQTTVersion(
- version: MQTTClient.Version?,
- strings: [String]
-) {
- withDependencies {
- $0.cliClient = .liveValue
- } operation: {
- @Dependency(\.cliClient) var cliClient
- for string in strings {
- #expect(cliClient.parseMqttClientVersion(string) == version)
- #expect(cliClient.parseMqttClientVersion("v\(string)") == version)
+ override func invokeTest() {
+ withDependencies {
+ $0.cliClient = .liveValue
+ $0.environment = .liveValue
+ $0.environment.processInfo = { [:] }
+ } operation: {
+ super.invokeTest()
}
}
-}
-@Test(
- 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))
- ]
-)
-func logLevelFromEnvVars(expectedLevel: Logger.Level, environment: EnvVars) {
- withDependencies {
- $0.cliClient = .liveValue
- } operation: {
+ func testParsingMQTTVersion() {
@Dependency(\.cliClient) var cliClient
- #expect(cliClient.logLevel(environment) == expectedLevel)
- }
-}
-@Test(
- arguments: [
- (
- CliClient.EnvVarsRequest(envFilePath: nil, logger: nil, version: nil),
- EnvVars()
- ),
- (
- CliClient.EnvVarsRequest(envFilePath: nil, logger: nil, version: "3"),
- EnvVars(version: "3")
- ),
- (
- CliClient.EnvVarsRequest(
- envFilePath: "Tests/CliClientTests/test.env",
- logger: nil,
- version: nil
+ 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)
+ }
+ }
+ }
+
+ 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()
),
- EnvVars(
- appEnv: .testing,
- host: "test.mqtt",
- port: "1234",
- identifier: "testing-mqtt",
- userName: "test-user",
- password: "super-secret",
- logLevel: .debug,
- version: "5.0"
+ (
+ 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
)
- )
- ]
-)
-func checkMakeEnvVars(
- request: CliClient.EnvVarsRequest,
- expectedEnvVars: EnvVars
-) async throws {
- try await withDependencies {
- $0.cliClient = .liveValue
- $0.environment = .liveValue
- $0.environment.processInfo = { [:] }
- } operation: {
- @Dependency(\.cliClient) var cliClient
- let result = try await cliClient.makeEnvVars(request)
- #expect(result == expectedEnvVars)
+ ]
+
+ 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 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)
}
}
+
+// - MARK: Helper
+
+private func cleanFilePath(_ path: String) -> String {
+ 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)
+}
+
+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"
+ )
+}
diff --git a/Tests/CliClientTests/test-env.json b/Tests/CliClientTests/test-env.json
new file mode 100755
index 0000000..7c625ec
--- /dev/null
+++ b/Tests/CliClientTests/test-env.json
@@ -0,0 +1,10 @@
+{
+ "APP_ENV": "testing",
+ "MQTT_HOST": "test.mqtt",
+ "MQTT_PORT": "1234",
+ "MQTT_IDENTIFIER": "testing-mqtt",
+ "MQTT_USERNAME": "test-user",
+ "MQTT_PASSWORD": "super-secret",
+ "LOG_LEVEL": "debug",
+ "MQTT_VERSION": "5.0"
+}
diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test
index cd9123b..ad06064 100644
--- a/docker/Dockerfile.test
+++ b/docker/Dockerfile.test
@@ -1,5 +1,6 @@
# Used to build a test image.
-FROM swift:5.10
+ARG SWIFT_IMAGE_VERSION="5.10"
+FROM swift:${SWIFT_IMAGE_VERSION}
WORKDIR /app
COPY ./Package.* ./
RUN swift package resolve