@@ -1,10 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Server;
@@ -95,21 +98,20 @@ namespace MQTTnet.Tests
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort);
TaskCompletionSource<bool> connected = new TaskCompletionSource<bool>();
managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e => { connected.SetResult(true);});
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.Build());
await connected.Task ;
await connected;
await managedClient.StopAsync();
Assert.AreEqual(0, (await server.GetClientStatusAsync()).Count);
}
}
[TestMethod]
public async Task Storage_Queue_Drains()
{
@@ -127,12 +129,7 @@ namespace MQTTnet.Tests
.WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage();
TaskCompletionSource<bool> connected = new TaskCompletionSource<bool>();
managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e =>
{
managedClient.ConnectedHandler = null;
connected.SetResult(true);
});
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
@@ -140,7 +137,7 @@ namespace MQTTnet.Tests
.WithAutoReconnectDelay(System.TimeSpan.FromSeconds(5))
.Build());
await connected.Task ;
await connected;
await testEnvironment.Server.StopAsync();
@@ -151,17 +148,12 @@ namespace MQTTnet.Tests
//in storage at this point (i.e. no waiting).
Assert.AreEqual(1, storage.GetMessageCount());
connected = new TaskCompletionSource<bool>();
managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e =>
{
managedClient.ConnectedHandler = null;
connected.SetResult(true);
});
connected = GetConnectedTask(managedClient);
await testEnvironment.Server.StartAsync(new MqttServerOptionsBuilder()
.WithDefaultEndpointPort(testEnvironment.ServerPort).Build());
await connected.Task ;
await connected;
//Wait 500ms here so the client has time to publish the queued message
await Task.Delay(500);
@@ -171,8 +163,235 @@ namespace MQTTnet.Tests
await managedClient.StopAsync();
}
}
[TestMethod]
public async Task Subscriptions_And_Unsubscriptions_Are_Made_And_Reestablished_At_Reconnect()
{
using (var testEnvironment = new TestEnvironment())
{
var unmanagedClient = testEnvironment.CreateClient();
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
var received = SetupReceivingOfMessages(managedClient, 2);
// Perform some opposing subscriptions and unsubscriptions to verify
// that these conflicting subscriptions are handled correctly
await managedClient.SubscribeAsync("keptSubscribed");
await managedClient.SubscribeAsync("subscribedThenUnsubscribed");
await managedClient.UnsubscribeAsync("subscribedThenUnsubscribed");
await managedClient.UnsubscribeAsync("unsubscribedThenSubscribed");
await managedClient.SubscribeAsync("unsubscribedThenSubscribed");
//wait a bit for the subscriptions to become established before the messages are published
await Task.Delay(500);
var sendingClient = await testEnvironment.ConnectClientAsync();
async Task PublishMessages()
{
await sendingClient.PublishAsync("keptSubscribed", new byte[] { 1 });
await sendingClient.PublishAsync("subscribedThenUnsubscribed", new byte[] { 1 });
await sendingClient.PublishAsync("unsubscribedThenSubscribed", new byte[] { 1 });
}
await PublishMessages();
async Task AssertMessagesReceived()
{
var messages = await received;
Assert.AreEqual("keptSubscribed", messages[0].Topic);
Assert.AreEqual("unsubscribedThenSubscribed", messages[1].Topic);
}
await AssertMessagesReceived();
var connected = GetConnectedTask(managedClient);
await unmanagedClient.DisconnectAsync();
// the managed client has to reconnect by itself
await connected;
// wait a bit so that the managed client can reestablish the subscriptions
await Task.Delay(500);
received = SetupReceivingOfMessages(managedClient, 2);
await PublishMessages();
// and then the same subscriptions need to exist again
await AssertMessagesReceived();
}
}
// This case also serves as a regression test for the previous behavior which re-published
// each and every existing subscriptions with every new subscription that was made
// (causing performance problems and having the visible symptom of retained messages being received again)
[TestMethod]
public async Task Subscriptions_Subscribe_Only_New_Subscriptions()
{
using (var testEnvironment = new TestEnvironment())
{
var managedClient = await CreateManagedClientAsync(testEnvironment);
var sendingClient = await testEnvironment.ConnectClientAsync();
await managedClient.SubscribeAsync("topic");
//wait a bit for the subscription to become established
await Task.Delay(500);
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var messages = await SetupReceivingOfMessages(managedClient, 1);
Assert.AreEqual(1, messages.Count);
Assert.AreEqual("topic", messages.Single().Topic);
await managedClient.SubscribeAsync("anotherTopic");
await Task.Delay(500);
// The subscription of the other topic must not trigger a re-subscription of the existing topic
// (and thus renewed receiving of the retained message)
Assert.AreEqual(1, messages.Count);
}
}
// This case also serves as a regression test for the previous behavior
// that subscriptions were only published at the ConnectionCheckInterval
[TestMethod]
public async Task Subscriptions_Are_Published_Immediately()
{
using (var testEnvironment = new TestEnvironment())
{
// Use a long connection check interval to verify that the subscriptions
// do not depend on the connection check interval anymore
var connectionCheckInterval = TimeSpan.FromSeconds(10);
var managedClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClientAsync();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
await managedClient.SubscribeAsync("topic");
var subscribeTime = DateTime.UtcNow;
var messages = await SetupReceivingOfMessages(managedClient, 1);
var elapsed = DateTime.UtcNow - subscribeTime;
Assert.IsTrue(elapsed < TimeSpan.FromSeconds(1), $"Subscriptions must be activated immediately, this one took {elapsed}");
Assert.AreEqual(messages.Single().Topic, "topic");
}
}
[TestMethod]
public async Task Subscriptions_Are_Cleared_At_Logout()
{
using (var testEnvironment = new TestEnvironment())
{
var managedClient = await CreateManagedClientAsync(testEnvironment);
var sendingClient = await testEnvironment.ConnectClientAsync();
await sendingClient.PublishAsync(new MqttApplicationMessage
{ Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
// Wait a bit for the retained message to be available
await Task.Delay(500);
await managedClient.SubscribeAsync("topic");
await SetupReceivingOfMessages(managedClient, 1);
await managedClient.StopAsync();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.WithAutoReconnectDelay(TimeSpan.FromSeconds(1))
.Build());
var messages = new List<MqttApplicationMessage>();
managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(r =>
{
messages.Add(r.ApplicationMessage);
});
await Task.Delay(500);
// After reconnect and then some delay, the retained message must not be received,
// showing that the subscriptions were cleared
Assert.AreEqual(0, messages.Count);
}
}
private async Task<ManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment,
IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null)
{
await testEnvironment.StartServerAsync();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort);
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.Build();
// Use a short connection check interval so that subscription operations are performed quickly
// in order to verify against a previous implementation that performed subscriptions only
// at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient =
new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetLogger().CreateChildLogger());
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(managedOptions);
await connected;
return managedClient;
}
/// <summary>
/// Returns a task that will finish when the <paramref name="managedClient"/> has connected
/// </summary>
private Task GetConnectedTask(ManagedMqttClient managedClient)
{
TaskCompletionSource<bool> connected = new TaskCompletionSource<bool>();
managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e =>
{
managedClient.ConnectedHandler = null;
connected.SetResult(true);
});
return connected.Task;
}
/// <summary>
/// Returns a task that will return the messages received on <paramref name="managedClient"/>
/// when <paramref name="expectedNumberOfMessages"/> have been received
/// </summary>
private Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
{
var receivedMessages = new List<MqttApplicationMessage>();
var allReceived = new TaskCompletionSource<List<MqttApplicationMessage>>();
managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(r =>
{
receivedMessages.Add(r.ApplicationMessage);
if (receivedMessages.Count == expectedNumberOfMessages)
{
allReceived.SetResult(receivedMessages);
}
});
return allReceived.Task;
}
}
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
{
private IList<ManagedMqttApplicationMessage> _messages = null;