Compare commits
12 Commits
6472d3cd1e
...
0.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
916fcb3584
|
|||
|
d9af0b8b30
|
|||
|
aa666d799a
|
|||
|
3825517dae
|
|||
|
c21695a37e
|
|||
|
3743eefa69
|
|||
|
845d566c60
|
|||
|
99f39b91af
|
|||
|
55ea88a29f
|
|||
|
756fd0bccf
|
|||
|
24f2ad63a7
|
|||
|
ce18c44363
|
68
.gitea/workflows/release.yml
Executable file
68
.gitea/workflows/release.yml
Executable file
@@ -0,0 +1,68 @@
|
||||
#
|
||||
name: Create and publish a Docker image
|
||||
|
||||
# Configures this workflow to run every time a change is pushed to the branch called `release`.
|
||||
on:
|
||||
push:
|
||||
branches: ['release']
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
|
||||
env:
|
||||
REGISTRY: git.housh.dev
|
||||
IMAGE_NAME: ${{ gitea.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
#
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.CONTAINER_TOKEN }}
|
||||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=sha
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)."
|
||||
# - name: Generate artifact attestation
|
||||
# uses: actions/attest-build-provenance@v1
|
||||
# with:
|
||||
# subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
# subject-digest: ${{ steps.push.outputs.digest }}
|
||||
# push-to-registry: true
|
||||
# github-token: ${{ secrets.CONTAINER_TOKEN }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,7 +5,7 @@
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.dewPoint-env
|
||||
.dewPoint-env*
|
||||
.topics
|
||||
mqtt_password.txt
|
||||
.env
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
disabled_rules:
|
||||
- closing_brace
|
||||
- fuction_body_length
|
||||
|
||||
included:
|
||||
- Sources
|
||||
- Tests
|
||||
|
||||
ignore_multiline_statement_conditions: true
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
},
|
||||
"testTargets" : [
|
||||
{
|
||||
"parallelizable" : true,
|
||||
"target" : {
|
||||
"containerPath" : "container:",
|
||||
"identifier" : "IntegrationTests",
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MQTTConnectionService"
|
||||
BuildableName = "MQTTConnectionService"
|
||||
BlueprintName = "MQTTConnectionService"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MQTTConnectionService"
|
||||
BuildableName = "MQTTConnectionService"
|
||||
BlueprintName = "MQTTConnectionService"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MQTTManager"
|
||||
BuildableName = "MQTTManager"
|
||||
BlueprintName = "MQTTManager"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MQTTManager"
|
||||
BuildableName = "MQTTManager"
|
||||
BlueprintName = "MQTTManager"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1330"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "Models"
|
||||
BuildableName = "Models"
|
||||
BlueprintName = "Models"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "Models"
|
||||
BuildableName = "Models"
|
||||
BlueprintName = "Models"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "SensorsService"
|
||||
BuildableName = "SensorsService"
|
||||
BlueprintName = "SensorsService"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "SensorsService"
|
||||
BuildableName = "SensorsService"
|
||||
BlueprintName = "SensorsService"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,177 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MQTTConnectionService"
|
||||
BuildableName = "MQTTConnectionService"
|
||||
BlueprintName = "MQTTConnectionService"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "MQTTManager"
|
||||
BuildableName = "MQTTManager"
|
||||
BlueprintName = "MQTTManager"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "Models"
|
||||
BuildableName = "Models"
|
||||
BlueprintName = "Models"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "SensorsService"
|
||||
BuildableName = "SensorsService"
|
||||
BlueprintName = "SensorsService"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "dewpoint-controller"
|
||||
BuildableName = "dewpoint-controller"
|
||||
BlueprintName = "dewpoint-controller"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "IntegrationTests"
|
||||
BuildableName = "IntegrationTests"
|
||||
BlueprintName = "IntegrationTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "CliClient"
|
||||
BuildableName = "CliClient"
|
||||
BlueprintName = "CliClient"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<TestPlans>
|
||||
<TestPlanReference
|
||||
reference = "container:.swiftpm/dewpoint-controller-Package.xctestplan"
|
||||
default = "YES">
|
||||
</TestPlanReference>
|
||||
</TestPlans>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "IntegrationTests"
|
||||
BuildableName = "IntegrationTests"
|
||||
BlueprintName = "IntegrationTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "dewpoint-controller"
|
||||
BuildableName = "dewpoint-controller"
|
||||
BlueprintName = "dewpoint-controller"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "dewpoint-controller"
|
||||
BuildableName = "dewpoint-controller"
|
||||
BlueprintName = "dewpoint-controller"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1610"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "dewpoint-controller"
|
||||
BuildableName = "dewpoint-controller"
|
||||
BlueprintName = "dewpoint-controller"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "dewpoint-controller"
|
||||
BuildableName = "dewpoint-controller"
|
||||
BlueprintName = "dewpoint-controller"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "dewpoint-controller"
|
||||
BuildableName = "dewpoint-controller"
|
||||
BlueprintName = "dewpoint-controller"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -42,6 +42,16 @@ let package = Package(
|
||||
.product(name: "MQTTNIO", package: "mqtt-nio")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "CliClientTests",
|
||||
dependencies: [
|
||||
"CliClient"
|
||||
],
|
||||
resources: [
|
||||
.copy("test.env"),
|
||||
.copy("test-env.json")
|
||||
]
|
||||
),
|
||||
.executableTarget(
|
||||
name: "DewPointController",
|
||||
dependencies: [
|
||||
|
||||
@@ -14,25 +14,49 @@ public extension DependencyValues {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the interface needed for the command line application.
|
||||
///
|
||||
///
|
||||
@DependencyClient
|
||||
public struct CliClient {
|
||||
|
||||
/// Parse a log level from the given `EnvVars`.
|
||||
public var logLevel: @Sendable (EnvVars) -> Logger.Level = { _ in .debug }
|
||||
|
||||
/// Generate the `EnvVars` with the given parameters.
|
||||
public var makeEnvVars: @Sendable (EnvVarsRequest) async throws -> EnvVars
|
||||
|
||||
/// Generate the `MQTTClient` with the given parameters.
|
||||
public var makeClient: @Sendable (ClientRequest) throws -> MQTTClient
|
||||
|
||||
/// Attempt to parse a string to an `MQTTClient.Version`.
|
||||
public var parseMqttClientVersion: @Sendable (String) -> MQTTClient.Version?
|
||||
|
||||
/// Represents the parameters needed to create an `MQTTClient`.
|
||||
///
|
||||
public struct ClientRequest: Sendable {
|
||||
public let envVars: EnvVars
|
||||
|
||||
/// The environment variables used to create the client.
|
||||
public let environment: EnvVars
|
||||
|
||||
/// The event loop group for the client.
|
||||
public let eventLoopGroup: MultiThreadedEventLoopGroup
|
||||
|
||||
/// A logger to use with the client.
|
||||
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(
|
||||
envVars: EnvVars,
|
||||
environment: EnvVars,
|
||||
eventLoopGroup: MultiThreadedEventLoopGroup,
|
||||
logger: Logger?
|
||||
) {
|
||||
self.envVars = envVars
|
||||
self.environment = environment
|
||||
self.eventLoopGroup = eventLoopGroup
|
||||
self.logger = logger
|
||||
}
|
||||
@@ -54,6 +78,7 @@ public struct CliClient {
|
||||
self.logger = logger
|
||||
self.mqttClientVersion = mqttClientVersion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +88,7 @@ extension CliClient: DependencyKey {
|
||||
Self(
|
||||
logLevel: { Logger.Level.from(environment: $0) },
|
||||
makeEnvVars: {
|
||||
try EnvVars.load(
|
||||
try await EnvVars.load(
|
||||
dotEnvFile: $0.envFilePath,
|
||||
logger: $0.logger,
|
||||
version: $0.mqttClientVersion
|
||||
@@ -71,7 +96,7 @@ extension CliClient: DependencyKey {
|
||||
},
|
||||
makeClient: {
|
||||
MQTTClient(
|
||||
envVars: $0.envVars,
|
||||
environment: $0.environment,
|
||||
eventLoopGroup: $0.eventLoopGroup,
|
||||
logger: $0.logger
|
||||
)
|
||||
@@ -81,35 +106,46 @@ 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 {
|
||||
|
||||
/// Load the `EnvVars` from the environment.
|
||||
///
|
||||
/// - Paramaters:
|
||||
/// - dotEnvFile: An optional environment file to load.
|
||||
/// - logger: An optional logger to use for debugging.
|
||||
/// - version: A version that is specified from command line, ignoring any environment variable.
|
||||
static func load(
|
||||
dotEnvFile: String?,
|
||||
logger: Logger?,
|
||||
version: String?
|
||||
) throws -> EnvVars {
|
||||
) async throws -> EnvVars {
|
||||
@Dependency(\.environment) var environment
|
||||
|
||||
let defaultEnvVars = EnvVars()
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
let coders = environment.coders()
|
||||
|
||||
if let dotEnvFile {
|
||||
try DotEnv.load(path: dotEnvFile)
|
||||
}
|
||||
|
||||
let defaultEnvDict = (try? encoder.encode(defaultEnvVars))
|
||||
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||
let defaultEnvDict = (try? coders.encode(defaultEnvVars))
|
||||
.flatMap { try? coders.decode([String: String].self, from: $0) }
|
||||
?? [:]
|
||||
|
||||
let dotEnvDict = try await environment.dotEnvDict(path: dotEnvFile)
|
||||
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))
|
||||
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
||||
.flatMap { try? coders.decode(EnvVars.self, from: $0) }
|
||||
?? defaultEnvVars
|
||||
|
||||
if let version {
|
||||
@@ -125,7 +161,7 @@ extension EnvVars {
|
||||
@_spi(Internal)
|
||||
public extension MQTTClient {
|
||||
convenience init(
|
||||
envVars: EnvVars,
|
||||
environment envVars: EnvVars,
|
||||
eventLoopGroup: EventLoopGroup,
|
||||
logger: Logger?
|
||||
) {
|
||||
@@ -136,7 +172,7 @@ public extension MQTTClient {
|
||||
eventLoopGroupProvider: .shared(eventLoopGroup),
|
||||
logger: logger,
|
||||
configuration: .init(
|
||||
version: .parseOrDefualt(string: envVars.version),
|
||||
version: .parseOrDefault(string: envVars.version),
|
||||
disablePing: false,
|
||||
userName: envVars.userName,
|
||||
password: envVars.password
|
||||
@@ -145,10 +181,11 @@ public extension MQTTClient {
|
||||
}
|
||||
}
|
||||
|
||||
extension MQTTClient.Version {
|
||||
@_spi(Internal)
|
||||
public extension MQTTClient.Version {
|
||||
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 {
|
||||
return .default
|
||||
}
|
||||
@@ -166,8 +203,7 @@ extension MQTTClient.Version {
|
||||
}
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public extension Logger.Level {
|
||||
extension Logger.Level {
|
||||
|
||||
/// Parse a `Logger.Level` from the loaded `EnvVars`.
|
||||
static func from(environment envVars: EnvVars) -> Self {
|
||||
|
||||
134
Sources/CliClient/EnvironmentDependency.swift
Normal file
134
Sources/CliClient/EnvironmentDependency.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import DotEnv
|
||||
import Foundation
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Responsible for loading environment variables and files.
|
||||
///
|
||||
///
|
||||
@_spi(Internal)
|
||||
@DependencyClient
|
||||
public struct EnvironmentDependency: Sendable {
|
||||
|
||||
public var coders: @Sendable () -> any Coderable = { JSONCoders() }
|
||||
|
||||
/// 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)
|
||||
|
||||
public 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DecodeError: Error {}
|
||||
|
||||
@_spi(Internal)
|
||||
extension EnvironmentDependency: DependencyKey {
|
||||
|
||||
public static let testValue: EnvironmentDependency = Self()
|
||||
|
||||
public static func live(
|
||||
decoder: JSONDecoder = .init(),
|
||||
encoder: JSONEncoder = .init()
|
||||
) -> Self {
|
||||
Self(
|
||||
coders: { JSONCoders(decoder: decoder, encoder: 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)
|
||||
)
|
||||
}
|
||||
},
|
||||
processInfo: { ProcessInfo.processInfo.environment }
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
#if os(Linux)
|
||||
return URL(fileURLWithPath: path)
|
||||
#else
|
||||
return URL(filePath: path)
|
||||
#endif
|
||||
}
|
||||
@@ -13,11 +13,11 @@ extension Application {
|
||||
|
||||
static let configuration: CommandConfiguration = .init(
|
||||
commandName: "debug",
|
||||
abstract: "Debug the environment variables."
|
||||
abstract: "Debug the environment variables and command line arguments."
|
||||
)
|
||||
|
||||
@OptionGroup
|
||||
var shared: SharedOptions
|
||||
var options: SharedOptions
|
||||
|
||||
@Flag(
|
||||
name: [.customLong("show-password")],
|
||||
@@ -31,7 +31,7 @@ extension Application {
|
||||
|
||||
print("--------------------------")
|
||||
print("Running debug command...")
|
||||
if let envFile = shared.envFile {
|
||||
if let envFile = options.envFile {
|
||||
print("Reading env file: \(envFile)")
|
||||
print("--------------------------")
|
||||
} else {
|
||||
@@ -41,12 +41,12 @@ extension Application {
|
||||
|
||||
print("Loading EnvVars")
|
||||
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)
|
||||
print("--------------------------")
|
||||
|
||||
if let logLevel = shared.logLevel, let level = logLevel() {
|
||||
print("Log Level option: \(level)")
|
||||
if let logLevel = options.logLevel {
|
||||
print("Log Level option: \(logLevel)")
|
||||
print("--------------------------")
|
||||
} else {
|
||||
print("Log Level option: nil")
|
||||
|
||||
@@ -23,14 +23,13 @@ extension Application {
|
||||
)
|
||||
|
||||
@OptionGroup
|
||||
var shared: SharedOptions
|
||||
var options: SharedOptions
|
||||
|
||||
mutating func run() async throws {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
|
||||
let eventloopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
var logger = Logger(label: "dewpoint-controller")
|
||||
let mqtt = try await setup(eventLoopGroup: eventloopGroup, logger: &logger)
|
||||
let (mqtt, logger) = try await cliClient.setupRun(options: options)
|
||||
logger.info("Setting up environment...")
|
||||
|
||||
do {
|
||||
try await withDependencies {
|
||||
@@ -48,6 +47,9 @@ extension Application {
|
||||
gracefulShutdownSignals: [.sigterm, .sigint],
|
||||
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.maximumGracefulShutdownDuration = .seconds(10)
|
||||
|
||||
@@ -57,30 +59,32 @@ extension Application {
|
||||
try await serviceGroup.run()
|
||||
}
|
||||
|
||||
// Here we've received a shutdown signal and shutdown all the
|
||||
// services.
|
||||
try await mqtt.shutdown()
|
||||
try await eventloopGroup.shutdownGracefully()
|
||||
} catch {
|
||||
try await eventloopGroup.shutdownGracefully()
|
||||
// If something fails, shutdown the mqtt client.
|
||||
try await mqtt.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setup(
|
||||
eventLoopGroup: MultiThreadedEventLoopGroup,
|
||||
logger: inout Logger
|
||||
) async throws -> MQTTClient {
|
||||
@Dependency(\.cliClient) var cliClient
|
||||
|
||||
let environment = try await cliClient.makeEnvVars(shared.envVarsRequest(logger: logger))
|
||||
logger.logLevel = cliClient.logLevel(environment)
|
||||
|
||||
return try cliClient.makeClient(.init(
|
||||
envVars: environment,
|
||||
private extension CliClient {
|
||||
func setupRun(
|
||||
eventLoopGroup: MultiThreadedEventLoopGroup = .init(numberOfThreads: 1),
|
||||
loggerLabel: String = "dewpoint-controller",
|
||||
options: Application.SharedOptions
|
||||
) async throws -> (MQTTClient, Logger) {
|
||||
var logger = Logger(label: loggerLabel)
|
||||
let environment = try await makeEnvVars(options.envVarsRequest(logger: logger))
|
||||
logger.logLevel = logLevel(environment)
|
||||
let client = try makeClient(.init(
|
||||
environment: environment,
|
||||
eventLoopGroup: eventLoopGroup,
|
||||
logger: logger
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return (client, logger)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
@@ -17,7 +17,7 @@ extension Application {
|
||||
name: [.short, .customLong("log-level")],
|
||||
help: "Set the logging level."
|
||||
)
|
||||
var logLevel: LogLevelContainer?
|
||||
var logLevelContainer: LogLevelContainer?
|
||||
|
||||
@Option(
|
||||
name: [.short, .long],
|
||||
@@ -29,6 +29,8 @@ extension Application {
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
212
Tests/CliClientTests/CliClientTests.swift
Normal file
212
Tests/CliClientTests/CliClientTests.swift
Normal file
@@ -0,0 +1,212 @@
|
||||
@_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<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 {}
|
||||
10
Tests/CliClientTests/test-env.json
Executable file
10
Tests/CliClientTests/test-env.json
Executable file
@@ -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"
|
||||
}
|
||||
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
|
||||
|
||||
final class IntegrationTests: XCTestCase {
|
||||
|
||||
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
|
||||
|
||||
static let logger: Logger = {
|
||||
@@ -25,7 +24,6 @@ final class IntegrationTests: XCTestCase {
|
||||
|
||||
override func invokeTest() {
|
||||
let client = createClient(identifier: "\(Self.self)")
|
||||
|
||||
withDependencies {
|
||||
$0.mqtt = .live(client: client, logger: Self.logger)
|
||||
$0.psychrometricClient = PsychrometricClient.liveValue
|
||||
@@ -36,7 +34,7 @@ final class IntegrationTests: XCTestCase {
|
||||
|
||||
func testConnectionServiceShutdown() async throws {
|
||||
@Dependency(\.mqtt) var mqtt
|
||||
|
||||
do {
|
||||
let service = MQTTConnectionService(logger: Self.logger)
|
||||
let task = Task { try await service.run() }
|
||||
defer { task.cancel() }
|
||||
@@ -50,11 +48,14 @@ final class IntegrationTests: XCTestCase {
|
||||
mqtt.shutdown()
|
||||
|
||||
try await Task.sleep(for: .milliseconds(500))
|
||||
|
||||
// check the connection is active here.
|
||||
try await mqtt.withClient { client in
|
||||
XCTAssertFalse(client.isActive())
|
||||
}
|
||||
} catch {
|
||||
mqtt.shutdown()
|
||||
try await Task.sleep(for: .milliseconds(500))
|
||||
}
|
||||
}
|
||||
|
||||
func testMQTTConnectionStream() async throws {
|
||||
@@ -188,11 +189,9 @@ final class IntegrationTests: XCTestCase {
|
||||
configuration: config
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// - MARK: Helpers
|
||||
|
||||
struct TopicNotFoundError: Error {}
|
||||
|
||||
actor ResultContainer: Sendable {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# run this with docker-compose run test
|
||||
name: swift-mqtt-dewpoint-test
|
||||
|
||||
services:
|
||||
test:
|
||||
build:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# run this with docker-compose run dewpoint_controller
|
||||
name: swift-mqtt-dewpoint
|
||||
|
||||
services:
|
||||
dewpoint_controller:
|
||||
container_name: dewpoint-controller
|
||||
|
||||
Reference in New Issue
Block a user