From 0ed6894f463bd8af9c9da7f2861684b066bde265 Mon Sep 17 00:00:00 2001 From: Rido Date: Fri, 4 Nov 2022 19:29:31 -0700 Subject: [PATCH] Add Support to Gateways (#81) * add compSettings to pi-sense sample * upd trx in ci (#76) * upd trx in ci * upd trx to 2.2.1 * fix aws connect test * Feat/aws (#75) * add aws samples, and retries * get update shadow * revisit AWS impl * review aws sample * upd aws readme Co-authored-by: ridomin * init WP from shadow * shadow versioning * rev v5 * clean warning * configure retained * missing retain in wp * allow crt certs * retain birth * add modules support for hub client (#79) * add modules support for hub client * add module tests * upd trx to 2.2.2 Co-authored-by: ridomin * Feat/gateway (#80) * add audience, topic rid as string * test mm with gw * fix tests * review x auth for gw * clean warnings * fix err with ToBytes Co-authored-by: rido-min --- .github/workflows/ci.yml | 2 +- .github/workflows/samples2docker.yml | 2 +- samples/iothub-sample/appsettings.json | 2 +- samples/memmon/Device.cs | 2 +- samples/memmon/{RidoFY23CA.pem => ca.pem} | 0 samples/mqtt-device/Device.cs | 1 + .../ShadowSerializer.cs | 2 ++ .../HubMqttClient.cs | 14 ++++++---- .../BrokerClientFactory.cs | 12 ++++---- .../WritableProperty.cs | 3 +- .../Binders/CloudToDeviceBinder.cs | 12 ++++---- .../Binders/DeviceToCloudBinder.cs | 4 +-- .../Binders/TopicParameters.cs | 2 +- .../Binders/TopicParser.cs | 8 ++---- .../Connections/ConnectionSettings.cs | 5 ++++ .../Connections/SasAuth.cs | 10 +++++-- .../Connections/WithAzureIoTHubCredentials.cs | 28 +++++++++---------- .../X509ClientCertificateLocator.cs | 2 +- .../HubClient/HubTelemetryUTF8JsonFixture.cs | 11 ++++++++ .../MockMqttClient.cs | 13 +++++++-- 20 files changed, 82 insertions(+), 53 deletions(-) rename samples/memmon/{RidoFY23CA.pem => ca.pem} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43ae16d..b008762 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - name: Process trx reports with default if: always() - uses: im-open/process-dotnet-test-results@v2.2.1 + uses: im-open/process-dotnet-test-results@v2.2.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/samples2docker.yml b/.github/workflows/samples2docker.yml index 0578d9e..ac184a4 100644 --- a/.github/workflows/samples2docker.yml +++ b/.github/workflows/samples2docker.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Login to GitHub Container Registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/samples/iothub-sample/appsettings.json b/samples/iothub-sample/appsettings.json index 7d3d775..1a9b412 100644 --- a/samples/iothub-sample/appsettings.json +++ b/samples/iothub-sample/appsettings.json @@ -7,6 +7,6 @@ } }, "ConnectionStrings": { - "cs": "HostName=rido-freetier.azure-devices.net;DeviceId=device23;SharedAccessKey=MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=" + "cs": "HostName=ridofree.azure-devices.net;DeviceId=leaf02;SharedAccessKey=+icdXc8+XlxlggkOXdpaK7lacuJHOKqdwok8ClkFWFY=;GatewayHostName=localhost;CaFile=c:/certs/localhost/ca.pem" } } diff --git a/samples/memmon/Device.cs b/samples/memmon/Device.cs index 2a6424c..6d39cb5 100644 --- a/samples/memmon/Device.cs +++ b/samples/memmon/Device.cs @@ -238,7 +238,7 @@ string RenderData() StringBuilder sb = new(); AppendLineWithPadRight(sb, " "); AppendLineWithPadRight(sb, $"{connectionSettings?.HostName}:{connectionSettings?.TcpPort}"); - AppendLineWithPadRight(sb, $"{connectionSettings.ClientId} (Auth:{connectionSettings.Auth}/ TLS:{connectionSettings.UseTls})"); + AppendLineWithPadRight(sb, $"{connectionSettings.ClientId} (Auth:{connectionSettings.Auth}/ TLS:{connectionSettings.UseTls}) GW: {connectionSettings.GatewayHostName}"); AppendLineWithPadRight(sb, " "); AppendLineWithPadRight(sb, string.Format("{0:8} | {1:15} | {2}", "Property", "Value".PadRight(15), "Version")); AppendLineWithPadRight(sb, string.Format("{0:8} | {1:15} | {2}", "--------", "-----".PadLeft(15, '-'), "------")); diff --git a/samples/memmon/RidoFY23CA.pem b/samples/memmon/ca.pem similarity index 100% rename from samples/memmon/RidoFY23CA.pem rename to samples/memmon/ca.pem diff --git a/samples/mqtt-device/Device.cs b/samples/mqtt-device/Device.cs index 066540e..f80e223 100644 --- a/samples/mqtt-device/Device.cs +++ b/samples/mqtt-device/Device.cs @@ -56,6 +56,7 @@ private async Task> Property_interval_UpdateHandler(int p) if (p > 0) { + client.Property_interval.Value = p; ack.Description = "desired notification accepted"; ack.Status = 200; ack.Version = client.Property_interval.Version; diff --git a/src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/ShadowSerializer.cs b/src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/ShadowSerializer.cs index 6e2231b..a6db33a 100644 --- a/src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/ShadowSerializer.cs +++ b/src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/ShadowSerializer.cs @@ -28,7 +28,9 @@ public static T FromString(string s) => JsonSerializer.Deserialize(s, })!; } +#pragma warning disable CA1822 // Mark members as static public byte[] ToBytes(T payload, string name = "", int? version = null) +#pragma warning restore CA1822 // Mark members as static { if (string.IsNullOrEmpty(name)) { diff --git a/src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/HubMqttClient.cs b/src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/HubMqttClient.cs index f3815dd..19d26f9 100644 --- a/src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/HubMqttClient.cs +++ b/src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/HubMqttClient.cs @@ -8,7 +8,6 @@ namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient public class HubMqttClient : IHubMqttClient { public IMqttClient Connection { get; set; } - public string InitialState { get; set; } = String.Empty; private readonly TwinRequestResponseBinder twinOperationsBinder; @@ -38,12 +37,17 @@ public Func OnPropertyUpdateReceived public Task GetTwinAsync(CancellationToken cancellationToken = default) => twinOperationsBinder.GetTwinAsync(cancellationToken); public Task UpdateTwinAsync(object payload, CancellationToken cancellationToken = default) => twinOperationsBinder.UpdateTwinAsync(payload, cancellationToken); - public Task SendTelemetryAsync(object payload, CancellationToken t = default) => - Connection.PublishBinaryAsync($"devices/{Connection.Options.ClientId}/messages/events/", + public async Task SendTelemetryAsync(object payload, CancellationToken t = default) + { + string clientSegment = Connection.Options.ClientId; + if (clientSegment.Contains("/")) //should be a module + { + clientSegment = clientSegment.Replace("/", "/modules/"); + } + return await Connection.PublishBinaryAsync($"devices/{clientSegment}/messages/events/", new UTF8JsonSerializer().ToBytes(payload), Protocol.MqttQualityOfServiceLevel.AtLeastOnce, false, t); - - + } } } diff --git a/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/BrokerClientFactory.cs b/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/BrokerClientFactory.cs index 93c2730..3061ce2 100644 --- a/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/BrokerClientFactory.cs +++ b/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/BrokerClientFactory.cs @@ -9,10 +9,10 @@ public static class BrokerClientFactory public static string NuGetPackageVersion => $"{ThisAssembly.NuGetPackageVersion}"; public static ConnectionSettings? ComputedSettings { get; private set; } - public static async Task CreateFromConnectionSettingsAsync(string connectinString, bool withBirth = true, CancellationToken cancellationToken = default) => + public static async Task CreateFromConnectionSettingsAsync(string connectinString, bool withBirth = false, CancellationToken cancellationToken = default) => await CreateFromConnectionSettingsAsync(new ConnectionSettings(connectinString), withBirth, cancellationToken); - public static async Task CreateFromConnectionSettingsAsync(ConnectionSettings cs, bool withBirth = true, CancellationToken cancellationToken = default) + public static async Task CreateFromConnectionSettingsAsync(ConnectionSettings cs, bool withBirth = false, CancellationToken cancellationToken = default) { MqttClient? mqtt = new MqttFactory().CreateMqttClient(MqttNetTraceLogger.CreateTraceLogger()) as MqttClient; var connAck = await mqtt!.ConnectAsync(new MqttClientOptionsBuilder() @@ -23,8 +23,8 @@ public static async Task CreateFromConnectionSettingsAsync(Connecti { throw new ApplicationException($"Cannot connect to {cs}"); } - if (withBirth) - { + //if (withBirth) + //{ var birthPayload = new UTF8JsonSerializer().ToBytes( new BirthConvention.BirthMessage(BirthConvention.ConnectionStatus.online) { @@ -34,12 +34,12 @@ public static async Task CreateFromConnectionSettingsAsync(Connecti var pubAck = await mqtt.PublishBinaryAsync( BirthConvention.BirthTopic(mqtt.Options.ClientId), birthPayload, - Protocol.MqttQualityOfServiceLevel.AtLeastOnce, true, cancellationToken); + Protocol.MqttQualityOfServiceLevel.AtLeastOnce, true, cancellationToken); //hack to disable retained in registry if (pubAck.ReasonCode != MqttClientPublishReasonCode.Success) { throw new ApplicationException($"Error publishing Birth {cs}"); } - } + //} return mqtt; } } diff --git a/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/WritableProperty.cs b/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/WritableProperty.cs index a5e072a..e8e434e 100644 --- a/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/WritableProperty.cs +++ b/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/WritableProperty.cs @@ -32,7 +32,8 @@ public async Task SendMessageAsync(Ack payload, CancellationToken cancellatio var prop = new ReadOnlyProperty>(_connection, _name) { TopicPattern = "device/{clientId}/props/{name}/ack", - WrapMessage = false + WrapMessage = false, + Retain = RetainResponse }; await prop.SendMessageAsync(payload, cancellationToken); } diff --git a/src/MQTTnet.Extensions.MultiCloud/Binders/CloudToDeviceBinder.cs b/src/MQTTnet.Extensions.MultiCloud/Binders/CloudToDeviceBinder.cs index cdc0207..eec8ac1 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Binders/CloudToDeviceBinder.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Binders/CloudToDeviceBinder.cs @@ -1,8 +1,6 @@ using MQTTnet.Client; using MQTTnet.Extensions.MultiCloud.Serializers; using MQTTnet.Protocol; -using System.Diagnostics; -using System.Text; namespace MQTTnet.Extensions.MultiCloud.Binders; @@ -11,10 +9,10 @@ public abstract class CloudToDeviceBinder : ICloudToDevice private readonly string _name; private readonly IMqttClient _connection; - protected bool UnwrapRequest = false; - protected bool WrapResponse = false; - protected bool RetainResponse = false; - protected bool CleanRetained = false; + public bool UnwrapRequest = false; + public bool WrapResponse = false; + public bool RetainResponse = false; + public bool CleanRetained = false; public Func>? OnMessage { get; set; } @@ -44,7 +42,7 @@ public CloudToDeviceBinder(IMqttClient connection, string name, IMessageSerializ if (resp != null) { byte[] responseBytes = serializer.ToBytes(resp, WrapResponse ? _name : string.Empty); - string? resTopic = responseTopicPattern?.Replace("{rid}", tp.Rid.ToString()).Replace("{version}", tp.Version.ToString()); + string? resTopic = responseTopicPattern?.Replace("{rid}", tp.Rid!).Replace("{version}", tp.Version.ToString()); _ = connection.PublishAsync( new MqttApplicationMessageBuilder() .WithTopic(resTopic) diff --git a/src/MQTTnet.Extensions.MultiCloud/Binders/DeviceToCloudBinder.cs b/src/MQTTnet.Extensions.MultiCloud/Binders/DeviceToCloudBinder.cs index 7c0c94d..38100d3 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Binders/DeviceToCloudBinder.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Binders/DeviceToCloudBinder.cs @@ -11,9 +11,9 @@ public abstract class DeviceToCloudBinder : IDeviceToCloud private readonly IMqttClient _connection; private readonly IMessageSerializer _messageSerializer; - public string TopicPattern = "device/{clientId}/telemetry"; + public string TopicPattern = String.Empty; public bool WrapMessage = false; - protected bool Retain = false; + public bool Retain = false; public DeviceToCloudBinder(IMqttClient mqttClient, string name) : this(mqttClient, name, new UTF8JsonSerializer()) { } diff --git a/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParameters.cs b/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParameters.cs index 20b1511..589b725 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParameters.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParameters.cs @@ -2,6 +2,6 @@ public class TopicParameters { - public int Rid { get; set; } + public string? Rid { get; set; } public int Version { get; set; } } diff --git a/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParser.cs b/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParser.cs index 0880436..50e682e 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParser.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Binders/TopicParser.cs @@ -8,7 +8,7 @@ public static TopicParameters ParseTopic(string topic) { var segments = topic.Split('/'); int twinVersion = -1; - int rid = -1; + string rid = string.Empty; if (topic.Contains('?')) { var qs = HttpUtility.ParseQueryString(segments[^1]); @@ -16,11 +16,7 @@ public static TopicParameters ParseTopic(string topic) { twinVersion = v; } - - if (int.TryParse(qs["$rid"], out int r)) - { - rid = r; - } + rid = Convert.ToString(qs["$rid"])!; } return new TopicParameters() { Rid = rid, Version = twinVersion }; } diff --git a/src/MQTTnet.Extensions.MultiCloud/Connections/ConnectionSettings.cs b/src/MQTTnet.Extensions.MultiCloud/Connections/ConnectionSettings.cs index b8d3024..83e27a7 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Connections/ConnectionSettings.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Connections/ConnectionSettings.cs @@ -42,6 +42,8 @@ public AuthType Auth public string? CaFile { get; set; } public bool DisableCrl { get; set; } + public string? GatewayHostName { get; set; } + public ConnectionSettings() { SasMinutes = Default_SasMinutes; @@ -50,6 +52,7 @@ public ConnectionSettings() UseTls = Default_UseTls == "true"; DisableCrl = Default_DisableCrl == "true"; CleanSession = Default_CleanSession == "true"; + GatewayHostName = string.Empty; } public static ConnectionSettings FromConnectionString(string cs) => new(cs); @@ -98,6 +101,7 @@ private void ParseConnectionString(string cs) UseTls = GetStringValue(map, nameof(UseTls), Default_UseTls) == "true"; CaFile = GetStringValue(map, nameof(CaFile)); DisableCrl = GetStringValue(map, nameof(DisableCrl), Default_DisableCrl) == "true"; + GatewayHostName = GetStringValue(map, nameof(GatewayHostName)); } private static void AppendIfNotEmpty(StringBuilder sb, string name, string val) @@ -129,6 +133,7 @@ public override string ToString() AppendIfNotEmpty(result, nameof(ModelId), ModelId!); AppendIfNotEmpty(result, nameof(ClientId), ClientId!); AppendIfNotEmpty(result, nameof(Auth), Auth!.ToString()); + AppendIfNotEmpty(result, nameof(GatewayHostName), GatewayHostName!.ToString()); result.Remove(result.Length - 1, 1); return result.ToString(); } diff --git a/src/MQTTnet.Extensions.MultiCloud/Connections/SasAuth.cs b/src/MQTTnet.Extensions.MultiCloud/Connections/SasAuth.cs index 09c064c..2cbda9f 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Connections/SasAuth.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Connections/SasAuth.cs @@ -16,12 +16,16 @@ internal static string Sign(string requestString, string key) internal static string CreateSasToken(string resource, string sasKey, int minutes) { - var expiry = DateTimeOffset.UtcNow.AddMinutes(minutes).ToUnixTimeSeconds().ToString(); var sig = System.Net.WebUtility.UrlEncode(Sign($"{resource}\n{expiry}", sasKey)); return $"SharedAccessSignature sr={resource}&sig={sig}&se={expiry}"; } - internal static (string username, string password) GenerateHubSasCredentials(string hostName, string deviceId, string sasKey, string modelId, int minutes = 60) => - (GetUserName(hostName, deviceId, modelId), CreateSasToken($"{hostName}/devices/{deviceId}", sasKey, minutes)); + internal static (string username, string password) GenerateHubSasCredentials(string hostName, string deviceId, string sasKey, string audience, string modelId, int minutes = 60) + { + string user = GetUserName(hostName, deviceId, modelId); + string pwd = CreateSasToken($"{audience}/devices/{deviceId}", sasKey, minutes); + return (user, pwd); + } + } diff --git a/src/MQTTnet.Extensions.MultiCloud/Connections/WithAzureIoTHubCredentials.cs b/src/MQTTnet.Extensions.MultiCloud/Connections/WithAzureIoTHubCredentials.cs index ea56cb4..71e0e9f 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Connections/WithAzureIoTHubCredentials.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Connections/WithAzureIoTHubCredentials.cs @@ -7,6 +7,11 @@ public static partial class MqttNetExtensions { internal static MqttClientOptionsBuilder WithAzureIoTHubCredentials(this MqttClientOptionsBuilder builder, ConnectionSettings? cs) { + string? hostName = cs!.HostName!; + if (!string.IsNullOrEmpty(cs.GatewayHostName)) + { + hostName = cs.GatewayHostName; + } if (cs?.Auth == AuthType.Sas) { if (string.IsNullOrEmpty(cs.ModuleId)) @@ -17,7 +22,8 @@ internal static MqttClientOptionsBuilder WithAzureIoTHubCredentials(this MqttCli { cs.ClientId = $"{cs.DeviceId}/{cs.ModuleId}"; } - return builder.WithAzureIoTHubCredentialsSas(cs.HostName!, cs.DeviceId!, cs.ModuleId!, cs.SharedAccessKey!, cs.ModelId!, cs.SasMinutes, cs.TcpPort); + builder.WithTlsSettings(cs); + return builder.WithAzureIoTHubCredentialsSas(hostName, cs.DeviceId!, cs.ModuleId!, cs.HostName!, cs.SharedAccessKey!, cs.ModelId!, cs.SasMinutes, cs.TcpPort); } else if (cs?.Auth == AuthType.X509) { @@ -34,8 +40,8 @@ internal static MqttClientOptionsBuilder WithAzureIoTHubCredentials(this MqttCli { cs.DeviceId = clientId; } - - return builder.WithAzureIoTHubCredentialsX509(cs.HostName!, cert, cs.ModelId!, cs.TcpPort); + builder.WithTlsSettings(cs); + return builder.WithAzureIoTHubCredentialsX509(hostName, cert, cs.ModelId!, cs.TcpPort); } else { @@ -43,22 +49,20 @@ internal static MqttClientOptionsBuilder WithAzureIoTHubCredentials(this MqttCli } } - public static MqttClientOptionsBuilder WithAzureIoTHubCredentialsSas(this MqttClientOptionsBuilder builder, string hostName, string deviceId, string moduleId, string sasKey, string modelId, int sasMinutes, int tcpPort) + public static MqttClientOptionsBuilder WithAzureIoTHubCredentialsSas(this MqttClientOptionsBuilder builder, string hostName, string deviceId, string moduleId, string audience, string sasKey, string modelId, int sasMinutes, int tcpPort) { if (string.IsNullOrEmpty(moduleId)) { - (string username, string password) = SasAuth.GenerateHubSasCredentials(hostName, deviceId, sasKey, modelId, sasMinutes); + (string username, string password) = SasAuth.GenerateHubSasCredentials(hostName, deviceId, sasKey, audience, modelId, sasMinutes); builder .WithTcpServer(hostName, tcpPort) - .WithTls() .WithCredentials(username, password); } else { - (string username, string password) = SasAuth.GenerateHubSasCredentials(hostName, $"{deviceId}/{moduleId}", sasKey, modelId, sasMinutes); + (string username, string password) = SasAuth.GenerateHubSasCredentials(hostName, $"{deviceId}/{moduleId}", sasKey, modelId, audience, sasMinutes); builder .WithTcpServer(hostName, tcpPort) - .WithTls() .WithCredentials(username, password); } return builder; @@ -70,13 +74,7 @@ public static MqttClientOptionsBuilder WithAzureIoTHubCredentialsX509(this MqttC builder .WithTcpServer(hostName, tcpPort) - .WithCredentials(new MqttClientCredentials(SasAuth.GetUserName(hostName, clientId, modelId))) - .WithTls(new MqttClientOptionsBuilderTlsParameters - { - UseTls = true, - SslProtocol = System.Security.Authentication.SslProtocols.Tls12, - Certificates = new List { cert } - }); + .WithCredentials(new MqttClientCredentials(SasAuth.GetUserName(hostName, clientId, modelId))); return builder; } diff --git a/src/MQTTnet.Extensions.MultiCloud/Connections/X509ClientCertificateLocator.cs b/src/MQTTnet.Extensions.MultiCloud/Connections/X509ClientCertificateLocator.cs index 5005a50..0c89eff 100644 --- a/src/MQTTnet.Extensions.MultiCloud/Connections/X509ClientCertificateLocator.cs +++ b/src/MQTTnet.Extensions.MultiCloud/Connections/X509ClientCertificateLocator.cs @@ -26,7 +26,7 @@ public static X509Certificate2 Load(string certSettings) } store.Close(); } - else if (certSettings.Contains(".pem|")) //mycert.pem|mycert.key + else if (certSettings.Contains(".pem|") || certSettings.Contains(".crt|")) //mycert.pem|mycert.key { var segments = certSettings.Split('|'); diff --git a/tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/HubTelemetryUTF8JsonFixture.cs b/tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/HubTelemetryUTF8JsonFixture.cs index 6a7a99a..f9f2ead 100644 --- a/tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/HubTelemetryUTF8JsonFixture.cs +++ b/tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/HubTelemetryUTF8JsonFixture.cs @@ -15,5 +15,16 @@ public async Task SendTelemetry() Assert.Equal("devices/mock/messages/events/", mqttClient.topicRecceived); Assert.Equal("{\"temp\":2}", mqttClient.payloadReceived); } + + [Fact] + public async Task SendTelemetryToModule() + { + var mqttClient = new MockMqttClient("mock/myModule"); + var hubMqttClient = new HubMqttClient(mqttClient); + //var telemetryBinder = new Telemetry(mqttClient, "temp"); + await hubMqttClient.SendTelemetryAsync(new { temp = 2}); + Assert.Equal("devices/mock/modules/myModule/messages/events/", mqttClient.topicRecceived); + Assert.Equal("{\"temp\":2}", mqttClient.payloadReceived); + } } } diff --git a/tests/MQTTnet.Extensions.MultiCloud.UnitTests/MockMqttClient.cs b/tests/MQTTnet.Extensions.MultiCloud.UnitTests/MockMqttClient.cs index 11ec89f..564b0b0 100644 --- a/tests/MQTTnet.Extensions.MultiCloud.UnitTests/MockMqttClient.cs +++ b/tests/MQTTnet.Extensions.MultiCloud.UnitTests/MockMqttClient.cs @@ -8,19 +8,28 @@ namespace MQTTnet.Extensions.MultiCloud.UnitTests { + internal class MockMqttClient : IMqttClient { + readonly string _clientId = ""; + + #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public MockMqttClient() -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { + _clientId = "mock"; + } + public MockMqttClient(string clientId) + { + _clientId = clientId; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public bool IsConnected => throw new NotImplementedException(); - public MqttClientOptions Options => new() { ClientId = "mock" }; + public MqttClientOptions Options => new() { ClientId = _clientId }; public string payloadReceived; public string topicRecceived;