This commit is contained in:
@@ -17,7 +17,6 @@
|
|||||||
},
|
},
|
||||||
"testTargets" : [
|
"testTargets" : [
|
||||||
{
|
{
|
||||||
"parallelizable" : true,
|
|
||||||
"target" : {
|
"target" : {
|
||||||
"containerPath" : "container:",
|
"containerPath" : "container:",
|
||||||
"identifier" : "IntegrationTests",
|
"identifier" : "IntegrationTests",
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ let package = Package(
|
|||||||
.product(name: "MQTTNIO", package: "mqtt-nio")
|
.product(name: "MQTTNIO", package: "mqtt-nio")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "CliClientTests",
|
||||||
|
dependencies: [
|
||||||
|
"CliClient"
|
||||||
|
],
|
||||||
|
resources: [
|
||||||
|
.copy("test.env")
|
||||||
|
]
|
||||||
|
),
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
name: "DewPointController",
|
name: "DewPointController",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|||||||
@@ -14,25 +14,38 @@ public extension DependencyValues {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the interface needed for the command line application.
|
||||||
|
///
|
||||||
|
///
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct CliClient {
|
public struct CliClient {
|
||||||
|
|
||||||
|
/// Parse a log level from the given `EnvVars`.
|
||||||
public var logLevel: @Sendable (EnvVars) -> Logger.Level = { _ in .debug }
|
public var logLevel: @Sendable (EnvVars) -> Logger.Level = { _ in .debug }
|
||||||
|
|
||||||
|
/// Generate the `EnvVars` with the given parameters.
|
||||||
public var makeEnvVars: @Sendable (EnvVarsRequest) async throws -> EnvVars
|
public var makeEnvVars: @Sendable (EnvVarsRequest) async throws -> EnvVars
|
||||||
|
|
||||||
|
/// Generate the `MQTTClient` with the given parameters.
|
||||||
public var makeClient: @Sendable (ClientRequest) throws -> MQTTClient
|
public var makeClient: @Sendable (ClientRequest) throws -> MQTTClient
|
||||||
|
|
||||||
|
/// Attempt to parse a string to an `MQTTClient.Version`.
|
||||||
public var parseMqttClientVersion: @Sendable (String) -> MQTTClient.Version?
|
public var parseMqttClientVersion: @Sendable (String) -> MQTTClient.Version?
|
||||||
|
|
||||||
|
/// Represents the parameters needed to create an `MQTTClient`.
|
||||||
|
///
|
||||||
|
///
|
||||||
public struct ClientRequest: Sendable {
|
public struct ClientRequest: Sendable {
|
||||||
public let envVars: EnvVars
|
public let environment: EnvVars
|
||||||
public let eventLoopGroup: MultiThreadedEventLoopGroup
|
public let eventLoopGroup: MultiThreadedEventLoopGroup
|
||||||
public let logger: Logger?
|
public let logger: Logger?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
envVars: EnvVars,
|
environment: EnvVars,
|
||||||
eventLoopGroup: MultiThreadedEventLoopGroup,
|
eventLoopGroup: MultiThreadedEventLoopGroup,
|
||||||
logger: Logger?
|
logger: Logger?
|
||||||
) {
|
) {
|
||||||
self.envVars = envVars
|
self.environment = environment
|
||||||
self.eventLoopGroup = eventLoopGroup
|
self.eventLoopGroup = eventLoopGroup
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
}
|
}
|
||||||
@@ -63,7 +76,7 @@ extension CliClient: DependencyKey {
|
|||||||
Self(
|
Self(
|
||||||
logLevel: { Logger.Level.from(environment: $0) },
|
logLevel: { Logger.Level.from(environment: $0) },
|
||||||
makeEnvVars: {
|
makeEnvVars: {
|
||||||
try EnvVars.load(
|
try await EnvVars.load(
|
||||||
dotEnvFile: $0.envFilePath,
|
dotEnvFile: $0.envFilePath,
|
||||||
logger: $0.logger,
|
logger: $0.logger,
|
||||||
version: $0.mqttClientVersion
|
version: $0.mqttClientVersion
|
||||||
@@ -71,7 +84,7 @@ extension CliClient: DependencyKey {
|
|||||||
},
|
},
|
||||||
makeClient: {
|
makeClient: {
|
||||||
MQTTClient(
|
MQTTClient(
|
||||||
envVars: $0.envVars,
|
environment: $0.environment,
|
||||||
eventLoopGroup: $0.eventLoopGroup,
|
eventLoopGroup: $0.eventLoopGroup,
|
||||||
logger: $0.logger
|
logger: $0.logger
|
||||||
)
|
)
|
||||||
@@ -81,32 +94,44 @@ extension CliClient: DependencyKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
extension EnvironmentDependency {
|
||||||
|
func dotEnvDict(path: String?) async throws -> [String: String] {
|
||||||
|
guard let path,
|
||||||
|
let file = FileType(path: path)
|
||||||
|
else { return [:] }
|
||||||
|
return try await load(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension EnvVars {
|
extension EnvVars {
|
||||||
|
|
||||||
/// Load the `EnvVars` from the environment.
|
/// Load the `EnvVars` from the environment.
|
||||||
///
|
///
|
||||||
/// - Paramaters:
|
/// - Paramaters:
|
||||||
|
/// - dotEnvFile: An optional environment file to load.
|
||||||
/// - logger: An optional logger to use for debugging.
|
/// - logger: An optional logger to use for debugging.
|
||||||
/// - version: A version that is specified from command line, ignoring any environment variable.
|
/// - version: A version that is specified from command line, ignoring any environment variable.
|
||||||
static func load(
|
static func load(
|
||||||
dotEnvFile: String?,
|
dotEnvFile: String?,
|
||||||
logger: Logger?,
|
logger: Logger?,
|
||||||
version: String?
|
version: String?
|
||||||
) throws -> EnvVars {
|
) async throws -> EnvVars {
|
||||||
|
@Dependency(\.environment) var environment
|
||||||
|
|
||||||
let defaultEnvVars = EnvVars()
|
let defaultEnvVars = EnvVars()
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
if let dotEnvFile {
|
|
||||||
try DotEnv.load(path: dotEnvFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
|
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
|
||||||
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||||
?? [:]
|
?? [:]
|
||||||
|
|
||||||
|
let dotEnvDict = try await environment.dotEnvDict(path: dotEnvFile)
|
||||||
let envVarsDict = defaultEnvDict
|
let envVarsDict = defaultEnvDict
|
||||||
.merging(ProcessInfo.processInfo.environment, uniquingKeysWith: { $1 })
|
.merging(environment.processInfo(), 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? decoder.decode(EnvVars.self, from: $0) }
|
||||||
@@ -125,7 +150,7 @@ extension EnvVars {
|
|||||||
@_spi(Internal)
|
@_spi(Internal)
|
||||||
public extension MQTTClient {
|
public extension MQTTClient {
|
||||||
convenience init(
|
convenience init(
|
||||||
envVars: EnvVars,
|
environment envVars: EnvVars,
|
||||||
eventLoopGroup: EventLoopGroup,
|
eventLoopGroup: EventLoopGroup,
|
||||||
logger: Logger?
|
logger: Logger?
|
||||||
) {
|
) {
|
||||||
|
|||||||
71
Sources/CliClient/EnvironmentDependency.swift
Normal file
71
Sources/CliClient/EnvironmentDependency.swift
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import DotEnv
|
||||||
|
import Foundation
|
||||||
|
import Models
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
public extension DependencyValues {
|
||||||
|
var environment: EnvironmentDependency {
|
||||||
|
get { self[EnvironmentDependency.self] }
|
||||||
|
set { self[EnvironmentDependency.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Responsible for loading environment variables and files.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
@_spi(Internal)
|
||||||
|
@DependencyClient
|
||||||
|
public struct EnvironmentDependency: Sendable {
|
||||||
|
|
||||||
|
/// Load the variables based on the request.
|
||||||
|
public var load: @Sendable (FileType) async throws -> [String: String] = { _ in [:] }
|
||||||
|
|
||||||
|
public var processInfo: @Sendable () -> [String: String] = { [:] }
|
||||||
|
|
||||||
|
public enum FileType: Equatable {
|
||||||
|
case dotEnv(path: String)
|
||||||
|
case json(path: String)
|
||||||
|
|
||||||
|
init?(path: String) {
|
||||||
|
let strings = path.split(separator: ".")
|
||||||
|
guard let ext = strings.last else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch ext {
|
||||||
|
case "env":
|
||||||
|
self = .dotEnv(path: path)
|
||||||
|
case "json":
|
||||||
|
self = .json(path: path)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@_spi(Internal)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,11 +13,11 @@ extension Application {
|
|||||||
|
|
||||||
static let configuration: CommandConfiguration = .init(
|
static let configuration: CommandConfiguration = .init(
|
||||||
commandName: "debug",
|
commandName: "debug",
|
||||||
abstract: "Debug the environment variables."
|
abstract: "Debug the environment variables and command line arguments."
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptionGroup
|
@OptionGroup
|
||||||
var shared: SharedOptions
|
var options: SharedOptions
|
||||||
|
|
||||||
@Flag(
|
@Flag(
|
||||||
name: [.customLong("show-password")],
|
name: [.customLong("show-password")],
|
||||||
@@ -31,7 +31,7 @@ extension Application {
|
|||||||
|
|
||||||
print("--------------------------")
|
print("--------------------------")
|
||||||
print("Running debug command...")
|
print("Running debug command...")
|
||||||
if let envFile = shared.envFile {
|
if let envFile = options.envFile {
|
||||||
print("Reading env file: \(envFile)")
|
print("Reading env file: \(envFile)")
|
||||||
print("--------------------------")
|
print("--------------------------")
|
||||||
} else {
|
} else {
|
||||||
@@ -41,12 +41,12 @@ extension Application {
|
|||||||
|
|
||||||
print("Loading EnvVars")
|
print("Loading EnvVars")
|
||||||
print("--------------------------")
|
print("--------------------------")
|
||||||
let envVars = try await client.makeEnvVars(shared.envVarsRequest(logger: logger))
|
let envVars = try await client.makeEnvVars(options.envVarsRequest(logger: logger))
|
||||||
printEnvVars(envVars: envVars, showPassword: showPassword)
|
printEnvVars(envVars: envVars, showPassword: showPassword)
|
||||||
print("--------------------------")
|
print("--------------------------")
|
||||||
|
|
||||||
if let logLevel = shared.logLevel, let level = logLevel() {
|
if let logLevel = options.logLevel {
|
||||||
print("Log Level option: \(level)")
|
print("Log Level option: \(logLevel)")
|
||||||
print("--------------------------")
|
print("--------------------------")
|
||||||
} else {
|
} else {
|
||||||
print("Log Level option: nil")
|
print("Log Level option: nil")
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ extension Application {
|
|||||||
)
|
)
|
||||||
|
|
||||||
@OptionGroup
|
@OptionGroup
|
||||||
var shared: SharedOptions
|
var options: SharedOptions
|
||||||
|
|
||||||
mutating func run() async throws {
|
mutating func run() async throws {
|
||||||
@Dependency(\.cliClient) var cliClient
|
@Dependency(\.cliClient) var cliClient
|
||||||
|
|
||||||
let eventloopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
let (mqtt, logger) = try await cliClient.setupRun(options: options)
|
||||||
var logger = Logger(label: "dewpoint-controller")
|
logger.info("Setting up environment...")
|
||||||
let mqtt = try await setup(eventLoopGroup: eventloopGroup, logger: &logger)
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
@@ -48,6 +47,9 @@ extension Application {
|
|||||||
gracefulShutdownSignals: [.sigterm, .sigint],
|
gracefulShutdownSignals: [.sigterm, .sigint],
|
||||||
logger: logger
|
logger: logger
|
||||||
)
|
)
|
||||||
|
// These settings prevent services from running forever after we've
|
||||||
|
// received a shutdown signal. In general it should not needed unless the
|
||||||
|
// services don't shutdown their async streams properly.
|
||||||
serviceGroupConfiguration.maximumCancellationDuration = .seconds(5)
|
serviceGroupConfiguration.maximumCancellationDuration = .seconds(5)
|
||||||
serviceGroupConfiguration.maximumGracefulShutdownDuration = .seconds(10)
|
serviceGroupConfiguration.maximumGracefulShutdownDuration = .seconds(10)
|
||||||
|
|
||||||
@@ -57,30 +59,32 @@ extension Application {
|
|||||||
try await serviceGroup.run()
|
try await serviceGroup.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here we've received a shutdown signal and shutdown all the
|
||||||
|
// services.
|
||||||
try await mqtt.shutdown()
|
try await mqtt.shutdown()
|
||||||
try await eventloopGroup.shutdownGracefully()
|
|
||||||
} catch {
|
} catch {
|
||||||
try await eventloopGroup.shutdownGracefully()
|
// If something fails, shutdown the mqtt client.
|
||||||
|
try await mqtt.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setup(
|
private extension CliClient {
|
||||||
eventLoopGroup: MultiThreadedEventLoopGroup,
|
func setupRun(
|
||||||
logger: inout Logger
|
eventLoopGroup: MultiThreadedEventLoopGroup = .init(numberOfThreads: 1),
|
||||||
) async throws -> MQTTClient {
|
loggerLabel: String = "dewpoint-controller",
|
||||||
@Dependency(\.cliClient) var cliClient
|
options: Application.SharedOptions
|
||||||
|
) async throws -> (MQTTClient, Logger) {
|
||||||
let environment = try await cliClient.makeEnvVars(shared.envVarsRequest(logger: logger))
|
var logger = Logger(label: loggerLabel)
|
||||||
logger.logLevel = cliClient.logLevel(environment)
|
let environment = try await makeEnvVars(options.envVarsRequest(logger: logger))
|
||||||
|
logger.logLevel = logLevel(environment)
|
||||||
return try cliClient.makeClient(.init(
|
let client = try makeClient(.init(
|
||||||
envVars: environment,
|
environment: environment,
|
||||||
eventLoopGroup: eventLoopGroup,
|
eventLoopGroup: eventLoopGroup,
|
||||||
logger: logger
|
logger: logger
|
||||||
))
|
))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return (client, logger)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ extension Application {
|
|||||||
name: [.short, .customLong("log-level")],
|
name: [.short, .customLong("log-level")],
|
||||||
help: "Set the logging level."
|
help: "Set the logging level."
|
||||||
)
|
)
|
||||||
var logLevel: LogLevelContainer?
|
var logLevelContainer: LogLevelContainer?
|
||||||
|
|
||||||
@Option(
|
@Option(
|
||||||
name: [.short, .long],
|
name: [.short, .long],
|
||||||
@@ -29,6 +29,8 @@ extension Application {
|
|||||||
.init(envFilePath: envFile, logger: logger, version: version)
|
.init(envFilePath: envFile, logger: logger, version: version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logLevel: Logger.Level? { logLevelContainer?.logLevel }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import Models
|
|
||||||
|
|
||||||
@_spi(Internal)
|
|
||||||
public extension Array where Element == TemperatureAndHumiditySensor {
|
|
||||||
static var live: Self {
|
|
||||||
TemperatureAndHumiditySensor.Location.allCases.map { location in
|
|
||||||
TemperatureAndHumiditySensor(location: location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -139,3 +139,11 @@ public struct TemperatureAndHumiditySensor: Identifiable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension Array where Element == TemperatureAndHumiditySensor {
|
||||||
|
static var live: Self {
|
||||||
|
TemperatureAndHumiditySensor.Location.allCases.map {
|
||||||
|
TemperatureAndHumiditySensor(location: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
99
Tests/CliClientTests/CliClientTests.swift
Normal file
99
Tests/CliClientTests/CliClientTests.swift
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
@_spi(Internal) import CliClient
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import Logging
|
||||||
|
import Models
|
||||||
|
import MQTTNIO
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func checkTesting() {
|
||||||
|
#expect(Bool(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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: {
|
||||||
|
@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
|
||||||
|
),
|
||||||
|
EnvVars(
|
||||||
|
appEnv: .testing,
|
||||||
|
host: "test.mqtt",
|
||||||
|
port: "1234",
|
||||||
|
identifier: "testing-mqtt",
|
||||||
|
userName: "test-user",
|
||||||
|
password: "super-secret",
|
||||||
|
logLevel: .debug,
|
||||||
|
version: "5.0"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Tests/CliClientTests/test.env
Normal file
8
Tests/CliClientTests/test.env
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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"
|
||||||
@@ -14,7 +14,6 @@ import ServiceLifecycleTestKit
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class IntegrationTests: XCTestCase {
|
final class IntegrationTests: XCTestCase {
|
||||||
|
|
||||||
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
|
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
|
||||||
|
|
||||||
static let logger: Logger = {
|
static let logger: Logger = {
|
||||||
@@ -25,7 +24,6 @@ final class IntegrationTests: XCTestCase {
|
|||||||
|
|
||||||
override func invokeTest() {
|
override func invokeTest() {
|
||||||
let client = createClient(identifier: "\(Self.self)")
|
let client = createClient(identifier: "\(Self.self)")
|
||||||
|
|
||||||
withDependencies {
|
withDependencies {
|
||||||
$0.mqtt = .live(client: client, logger: Self.logger)
|
$0.mqtt = .live(client: client, logger: Self.logger)
|
||||||
$0.psychrometricClient = PsychrometricClient.liveValue
|
$0.psychrometricClient = PsychrometricClient.liveValue
|
||||||
@@ -36,7 +34,7 @@ final class IntegrationTests: XCTestCase {
|
|||||||
|
|
||||||
func testConnectionServiceShutdown() async throws {
|
func testConnectionServiceShutdown() async throws {
|
||||||
@Dependency(\.mqtt) var mqtt
|
@Dependency(\.mqtt) var mqtt
|
||||||
|
do {
|
||||||
let service = MQTTConnectionService(logger: Self.logger)
|
let service = MQTTConnectionService(logger: Self.logger)
|
||||||
let task = Task { try await service.run() }
|
let task = Task { try await service.run() }
|
||||||
defer { task.cancel() }
|
defer { task.cancel() }
|
||||||
@@ -50,11 +48,14 @@ final class IntegrationTests: XCTestCase {
|
|||||||
mqtt.shutdown()
|
mqtt.shutdown()
|
||||||
|
|
||||||
try await Task.sleep(for: .milliseconds(500))
|
try await Task.sleep(for: .milliseconds(500))
|
||||||
|
|
||||||
// check the connection is active here.
|
// check the connection is active here.
|
||||||
try await mqtt.withClient { client in
|
try await mqtt.withClient { client in
|
||||||
XCTAssertFalse(client.isActive())
|
XCTAssertFalse(client.isActive())
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
mqtt.shutdown()
|
||||||
|
try await Task.sleep(for: .milliseconds(500))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMQTTConnectionStream() async throws {
|
func testMQTTConnectionStream() async throws {
|
||||||
@@ -188,11 +189,9 @@ final class IntegrationTests: XCTestCase {
|
|||||||
configuration: config
|
configuration: config
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - MARK: Helpers
|
// - MARK: Helpers
|
||||||
|
|
||||||
struct TopicNotFoundError: Error {}
|
struct TopicNotFoundError: Error {}
|
||||||
|
|
||||||
actor ResultContainer: Sendable {
|
actor ResultContainer: Sendable {
|
||||||
|
|||||||
Reference in New Issue
Block a user