private async Task ReceiveSingleMessageUsingCallbackAsync(TestDeviceType type, Client.TransportType transport) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); using (msg) { await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); testDeviceCallbackHandler.ExpectedMessageSentByService = msg; using var cts = new CancellationTokenSource(s_tenSeconds); Logger.Trace($"Sending C2D message from service, messageId={msg.MessageId}"); await Task .WhenAll( serviceClient.SendAsync(testDevice.Id, msg), testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token)) .ConfigureAwait(false); } await deviceClient.CloseAsync().ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false); }
private async Task ReceiveMessageWithCallbackRecoveryAsync( TestDeviceType type, Client.TransportType transport, string faultType, string reason, TimeSpan delayInSec, string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { await serviceClient.OpenAsync().ConfigureAwait(false); testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); } async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { var timeout = TimeSpan.FromSeconds(20); using var cts = new CancellationTokenSource(timeout); (Message message, string payload, string p1Value) = MessageReceiveE2ETests.ComposeC2dTestMessage(Logger); testDeviceCallbackHandler.ExpectedMessageSentByService = message; await serviceClient.SendAsync(testDevice.Id, message).ConfigureAwait(false); Client.Message receivedMessage = await deviceClient.ReceiveAsync(timeout).ConfigureAwait(false); await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); receivedMessage.Should().BeNull(); } Task CleanupOperationAsync() { serviceClient.CloseAsync(); testDeviceCallbackHandler?.Dispose(); return(Task.FromResult(true)); } await FaultInjection .TestErrorInjectionAsync( DevicePrefix, type, transport, proxyAddress, faultType, reason, delayInSec, FaultInjection.DefaultFaultDuration, InitOperationAsync, TestOperationAsync, CleanupOperationAsync, Logger) .ConfigureAwait(false); }
private async Task Twin_DeviceDesiredPropertyUpdateRecoveryAsync( Client.TransportType transport, string faultType, string reason, int delayInSec) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; var registryManager = RegistryManager.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); using var cts = new CancellationTokenSource(FaultInjection.RecoveryTimeMilliseconds); var propName = Guid.NewGuid().ToString(); var props = new TwinCollection(); // Configure the callback and start accepting twin changes. Func <DeviceClient, TestDevice, Task> initOperation = async(deviceClient, testDevice) => { testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, Logger); await testDeviceCallbackHandler.SetTwinPropertyUpdateCallbackHandlerAsync(propName).ConfigureAwait(false); }; // Change the twin from the service side and verify the device received it. Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) => { var propValue = Guid.NewGuid().ToString(); testDeviceCallbackHandler.ExpectedTwinPropertyValue = propValue; Logger.Trace($"{nameof(Twin_DeviceDesiredPropertyUpdateRecoveryAsync)}: name={propName}, value={propValue}"); Task serviceSendTask = RegistryManagerUpdateDesiredPropertyAsync(testDevice.Id, propName, propValue); Task twinReceivedTask = testDeviceCallbackHandler.WaitForTwinCallbackAsync(cts.Token); var tasks = new List <Task>() { serviceSendTask, twinReceivedTask }; while (tasks.Count > 0) { Task completedTask = await Task.WhenAny(tasks).ConfigureAwait(false); completedTask.GetAwaiter().GetResult(); tasks.Remove(completedTask); } }; await FaultInjection .TestErrorInjectionAsync( s_devicePrefix, TestDeviceType.Sasl, transport, faultType, reason, delayInSec, FaultInjection.DefaultDurationInSec, initOperation, testOperation, () => { return(Task.FromResult(false)); }, Logger) .ConfigureAwait(false); }
private async Task SendMethodAndRespondRecoveryAsync(Client.TransportType transport, string faultType, string reason, TimeSpan delayInSec, string proxyAddress = null) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; using var cts = new CancellationTokenSource(FaultInjection.RecoveryTime); // Configure the callback and start accepting method calls. async Task InitOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); await testDeviceCallbackHandler .SetDeviceReceiveMethodAsync(MethodName, DeviceResponseJson, ServiceRequestJson) .ConfigureAwait(false); } // Call the method from the service side and verify the device received the call. async Task TestOperationAsync(DeviceClient deviceClient, TestDevice testDevice) { Task serviceSendTask = ServiceSendMethodAndVerifyResponseAsync(testDevice.Id, MethodName, DeviceResponseJson, ServiceRequestJson); Task methodReceivedTask = testDeviceCallbackHandler.WaitForMethodCallbackAsync(cts.Token); var tasks = new List <Task>() { serviceSendTask, methodReceivedTask }; while (tasks.Count > 0) { Task completedTask = await Task.WhenAny(tasks).ConfigureAwait(false); completedTask.GetAwaiter().GetResult(); tasks.Remove(completedTask); } } // Cleanup references. Task CleanupOperationAsync() { testDeviceCallbackHandler?.Dispose(); return(Task.FromResult(false)); } await FaultInjection .TestErrorInjectionAsync( DevicePrefix, TestDeviceType.Sasl, transport, proxyAddress, faultType, reason, delayInSec, FaultInjection.DefaultFaultDelay, InitOperationAsync, TestOperationAsync, CleanupOperationAsync, Logger) .ConfigureAwait(false); }
private async Task DoNotReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType type, ITransportSettings[] settings) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(settings); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); // Subscribe to receive C2D messages over the callback. await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); // Now dispose and reinitialize the client instance. deviceClient.Dispose(); deviceClient = null; testDeviceCallbackHandler.Dispose(); testDeviceCallbackHandler = null; deviceClient = testDevice.CreateDeviceClient(settings); testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); // Open the device client - for MQTT, this will connect the device with CleanSession flag set to true. // This will ensure that messages sent before the device had subscribed to c2d topic are not delivered. await deviceClient.OpenAsync().ConfigureAwait(false); // Send the message from service. Logger.Trace($"Sending C2D message from service, messageId={msg.MessageId}"); await serviceClient.SendAsync(testDevice.Id, msg).ConfigureAwait(false); // Subscribe to receive C2D messages over the callback. testDeviceCallbackHandler.ExpectedMessageSentByService = msg; await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); // Wait to ensure that the message was not received. using var cts = new CancellationTokenSource(s_tenSeconds); Func <Task> receiveMessageOverCallback = async() => { await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); }; receiveMessageOverCallback.Should().Throw <OperationCanceledException>(); await serviceClient.CloseAsync().ConfigureAwait(false); deviceClient.Dispose(); testDeviceCallbackHandler.Dispose(); }
private async Task SendMethodAndRespondRecoveryAsync(Client.TransportType transport, string faultType, string reason, int delayInSec) { TestDeviceCallbackHandler testDeviceCallbackHandler = null; using var cts = new CancellationTokenSource(FaultInjection.RecoveryTimeMilliseconds); // Configure the callback and start accepting method calls. Func <DeviceClient, TestDevice, Task> initOperation = async(deviceClient, testDevice) => { testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, Logger); await testDeviceCallbackHandler .SetDeviceReceiveMethodAsync(MethodName, DeviceResponseJson, ServiceRequestJson) .ConfigureAwait(false); }; // Call the method from the service side and verify the device received the call. Func <DeviceClient, TestDevice, Task> testOperation = async(deviceClient, testDevice) => { Task serviceSendTask = ServiceSendMethodAndVerifyResponseAsync(testDevice.Id, MethodName, DeviceResponseJson, ServiceRequestJson); Task methodReceivedTask = testDeviceCallbackHandler.WaitForMethodCallbackAsync(cts.Token); var tasks = new List <Task>() { serviceSendTask, methodReceivedTask }; while (tasks.Count > 0) { Task completedTask = await Task.WhenAny(tasks).ConfigureAwait(false); completedTask.GetAwaiter().GetResult(); tasks.Remove(completedTask); } }; await FaultInjection .TestErrorInjectionAsync( DevicePrefix, TestDeviceType.Sasl, transport, faultType, reason, delayInSec, FaultInjection.DefaultDelayInSec, initOperation, testOperation, () => { return(Task.FromResult <bool>(false)); }, Logger) .ConfigureAwait(false); }
private async Task ReceiveMessagesSentBeforeSubscriptionAsync(TestDeviceType type, Client.TransportType transport) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); (Message msg, string payload, string p1Value) = ComposeC2dTestMessage(Logger); // Subscribe to receive C2D messages over the callback. await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); // Now dispose and reinitialize the client instance. deviceClient.Dispose(); deviceClient = null; testDeviceCallbackHandler.Dispose(); testDeviceCallbackHandler = null; deviceClient = testDevice.CreateDeviceClient(transport); testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); // Open the device client - for MQTT, this will connect the device with CleanSession flag set to false. await deviceClient.OpenAsync().ConfigureAwait(false); // Send the message from service. Logger.Trace($"Sending C2D message from service, messageId={msg.MessageId}"); await serviceClient.SendAsync(testDevice.Id, msg).ConfigureAwait(false); // Subscribe to receive C2D messages over the callback. testDeviceCallbackHandler.ExpectedMessageSentByService = msg; await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); // Wait to ensure that the message was received. using var cts = new CancellationTokenSource(s_tenSeconds); await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false); deviceClient.Dispose(); testDeviceCallbackHandler.Dispose(); }
public const int TestSuccessRate = 80; // 4 out of 5 (80%) test runs should pass (even after accounting for network instability issues). public static async Task TestPoolAmqpAsync( string devicePrefix, Client.TransportType transport, int poolSize, int devicesCount, Func <DeviceClient, TestDevice, TestDeviceCallbackHandler, Task> initOperation, Func <DeviceClient, TestDevice, TestDeviceCallbackHandler, Task> testOperation, Func <Task> cleanupOperation, ConnectionStringAuthScope authScope, bool ignoreConnectionStatus, MsTestLogger logger) { var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(transport) { AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings() { MaxPoolSize = unchecked ((uint)poolSize), Pooling = true } } }; int totalRuns = 0; int successfulRuns = 0; int currentSuccessRate = 0; bool reRunTest = false; var testDevices = new List <TestDevice>(); var deviceClients = new List <DeviceClient>(); var testDeviceCallbackHandlers = new List <TestDeviceCallbackHandler>(); var amqpConnectionStatuses = new List <AmqpConnectionStatusChange>(); var operations = new List <Task>(); do { totalRuns++; // Arrange // Initialize the test device client instances // Set the device client connection status change handler logger.Trace($">>> {nameof(PoolingOverAmqp)} Initializing Device Clients for multiplexing test - Test run {totalRuns}"); for (int i = 0; i < devicesCount; i++) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, $"{devicePrefix}_{i}_").ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings, authScope); var amqpConnectionStatusChange = new AmqpConnectionStatusChange(logger); deviceClient.SetConnectionStatusChangesHandler(amqpConnectionStatusChange.ConnectionStatusChangesHandler); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, logger); testDevices.Add(testDevice); deviceClients.Add(deviceClient); testDeviceCallbackHandlers.Add(testDeviceCallbackHandler); amqpConnectionStatuses.Add(amqpConnectionStatusChange); if (initOperation != null) { operations.Add(initOperation(deviceClient, testDevice, testDeviceCallbackHandler)); } } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); try { for (int i = 0; i < devicesCount; i++) { operations.Add(testOperation(deviceClients[i], testDevices[i], testDeviceCallbackHandlers[i])); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); // Close the device client instances and verify the connection status change checks bool deviceConnectionStatusAsExpected = true; for (int i = 0; i < devicesCount; i++) { await deviceClients[i].CloseAsync().ConfigureAwait(false); if (!ignoreConnectionStatus) { // The connection status change count should be 2: connect (open) and disabled (close) if (amqpConnectionStatuses[i].ConnectionStatusChangesHandlerCount != 2) { deviceConnectionStatusAsExpected = false; } // The connection status should be "Disabled", with connection status change reason "Client_close" Assert.AreEqual( ConnectionStatus.Disabled, amqpConnectionStatuses[i].LastConnectionStatus, $"The actual connection status is = {amqpConnectionStatuses[i].LastConnectionStatus}"); Assert.AreEqual( ConnectionStatusChangeReason.Client_Close, amqpConnectionStatuses[i].LastConnectionStatusChangeReason, $"The actual connection status change reason is = {amqpConnectionStatuses[i].LastConnectionStatusChangeReason}"); } } if (deviceConnectionStatusAsExpected) { successfulRuns++; } currentSuccessRate = (int)((double)successfulRuns / totalRuns * 100); reRunTest = currentSuccessRate < TestSuccessRate; } finally { // Close the service-side components and dispose the device client instances. if (cleanupOperation != null) { await cleanupOperation().ConfigureAwait(false); } deviceClients.ForEach(deviceClient => deviceClient.Dispose()); testDeviceCallbackHandlers.ForEach(testDeviceCallbackHandler => testDeviceCallbackHandler.Dispose()); // Clean up the local lists testDevices.Clear(); deviceClients.Clear(); amqpConnectionStatuses.Clear(); } } while (reRunTest && totalRuns < MaxTestRunCount); Assert.IsFalse(reRunTest, $"Device client instances got disconnected in {totalRuns - successfulRuns} runs out of {totalRuns}; current testSuccessRate = {currentSuccessRate}%."); }
public static async Task TestFaultInjectionPoolAmqpAsync( string devicePrefix, Client.TransportType transport, string proxyAddress, int poolSize, int devicesCount, string faultType, string reason, int delayInSec, int durationInSec, Func <DeviceClient, TestDevice, TestDeviceCallbackHandler, Task> initOperation, Func <DeviceClient, TestDevice, TestDeviceCallbackHandler, Task> testOperation, Func <IList <DeviceClient>, Task> cleanupOperation, ConnectionStringAuthScope authScope, MsTestLogger logger) { var transportSettings = new ITransportSettings[] { new AmqpTransportSettings(transport) { AmqpConnectionPoolSettings = new AmqpConnectionPoolSettings() { MaxPoolSize = unchecked ((uint)poolSize), Pooling = true, }, Proxy = proxyAddress == null ? null : new WebProxy(proxyAddress), } }; IList <TestDevice> testDevices = new List <TestDevice>(); IList <DeviceClient> deviceClients = new List <DeviceClient>(); IList <TestDeviceCallbackHandler> testDeviceCallbackHandlers = new List <TestDeviceCallbackHandler>(); IList <AmqpConnectionStatusChange> amqpConnectionStatuses = new List <AmqpConnectionStatusChange>(); IList <Task> operations = new List <Task>(); // Arrange // Initialize the test device client instances // Set the device client connection status change handler logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)} Initializing Device Clients for multiplexing test."); for (int i = 0; i < devicesCount; i++) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(logger, $"{devicePrefix}_{i}_").ConfigureAwait(false); DeviceClient deviceClient = testDevice.CreateDeviceClient(transportSettings, authScope); var amqpConnectionStatusChange = new AmqpConnectionStatusChange(testDevice.Id, logger); deviceClient.SetConnectionStatusChangesHandler(amqpConnectionStatusChange.ConnectionStatusChangesHandler); var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, logger); testDevices.Add(testDevice); deviceClients.Add(deviceClient); testDeviceCallbackHandlers.Add(testDeviceCallbackHandler); amqpConnectionStatuses.Add(amqpConnectionStatusChange); operations.Add(initOperation(deviceClient, testDevice, testDeviceCallbackHandler)); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); var watch = new Stopwatch(); try { // Act-Assert // Perform the test operation and verify the operation is successful // Perform baseline test operation for (int i = 0; i < devicesCount; i++) { logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)}: Performing baseline operation for device {i}."); operations.Add(testOperation(deviceClients[i], testDevices[i], testDeviceCallbackHandlers[i])); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); int countBeforeFaultInjection = amqpConnectionStatuses[0].ConnectionStatusChangeCount; // Inject the fault into device 0 watch.Start(); logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: {testDevices[0].Id} Requesting fault injection type={faultType} reason={reason}, delay={delayInSec}s, duration={durationInSec}s"); var faultInjectionMessage = FaultInjection.ComposeErrorInjectionProperties(faultType, reason, delayInSec, durationInSec); await deviceClients[0].SendEventAsync(faultInjectionMessage).ConfigureAwait(false); logger.Trace($"{nameof(FaultInjection)}: Waiting for fault injection to be active: {delayInSec} seconds."); await Task.Delay(TimeSpan.FromSeconds(delayInSec)).ConfigureAwait(false); // For disconnect type faults, the faulted device should disconnect and all devices should recover. if (FaultInjection.FaultShouldDisconnect(faultType)) { logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirming fault injection has been actived."); // Check that service issued the fault to the faulting device [device 0] bool isFaulted = false; for (int i = 0; i < FaultInjection.LatencyTimeBufferInSec; i++) { if (amqpConnectionStatuses[0].ConnectionStatusChangeCount > countBeforeFaultInjection) { isFaulted = true; break; } await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } Assert.IsTrue(isFaulted, $"The device {testDevices[0].Id} did not get faulted with fault type: {faultType}"); logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirmed fault injection has been actived."); // Check all devices are back online logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirming all devices back online."); bool notRecovered = true; int j = 0; for (int i = 0; notRecovered && i < durationInSec + FaultInjection.LatencyTimeBufferInSec; i++) { notRecovered = false; for (j = 0; j < devicesCount; j++) { if (amqpConnectionStatuses[j].LastConnectionStatus != ConnectionStatus.Connected) { await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); notRecovered = true; break; } } } if (notRecovered) { Assert.Fail($"{testDevices[j].Id} did not reconnect."); } logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Confirmed all devices back online."); // Perform the test operation for all devices for (int i = 0; i < devicesCount; i++) { logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)}: Performing test operation for device {i}."); operations.Add(testOperation(deviceClients[i], testDevices[i], testDeviceCallbackHandlers[i])); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); } else { logger.Trace($"{nameof(FaultInjectionPoolingOverAmqp)}: Performing test operation while fault injection is being activated."); // Perform the test operation for the faulted device multi times. for (int i = 0; i < FaultInjection.LatencyTimeBufferInSec; i++) { logger.Trace($">>> {nameof(FaultInjectionPoolingOverAmqp)}: Performing test operation for device 0 - Run {i}."); await testOperation(deviceClients[0], testDevices[0], testDeviceCallbackHandlers[0]).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } } // Close the device client instances for (int i = 0; i < devicesCount; i++) { operations.Add(deviceClients[i].CloseAsync()); } await Task.WhenAll(operations).ConfigureAwait(false); operations.Clear(); // Verify the connection status change checks. // For all of the devices - last connection status should be "Disabled", with reason "Client_close" for (int i = 0; i < devicesCount; i++) { // For the faulted device [device 0] - verify the connection status change count. if (i == 0) { if (FaultInjection.FaultShouldDisconnect(faultType)) { // 4 is the minimum notification count: connect, fault, reconnect, disable. Assert.IsTrue(amqpConnectionStatuses[i].ConnectionStatusChangeCount >= 4, $"The expected connection status change count for {testDevices[i].Id} should equals or greater than 4 but was {amqpConnectionStatuses[i].ConnectionStatusChangeCount}"); } else { // 2 is the minimum notification count: connect, disable. Assert.IsTrue(amqpConnectionStatuses[i].ConnectionStatusChangeCount >= 2, $"The expected connection status change count for {testDevices[i].Id} should be 2 but was {amqpConnectionStatuses[i].ConnectionStatusChangeCount}"); } } Assert.AreEqual(ConnectionStatus.Disabled, amqpConnectionStatuses[i].LastConnectionStatus, $"The expected connection status should be {ConnectionStatus.Disabled} but was {amqpConnectionStatuses[i].LastConnectionStatus}"); Assert.AreEqual(ConnectionStatusChangeReason.Client_Close, amqpConnectionStatuses[i].LastConnectionStatusChangeReason, $"The expected connection status change reason should be {ConnectionStatusChangeReason.Client_Close} but was {amqpConnectionStatuses[i].LastConnectionStatusChangeReason}"); } } finally { // Close the service-side components and dispose the device client instances. await cleanupOperation(deviceClients).ConfigureAwait(false); watch.Stop(); int timeToFinishFaultInjection = durationInSec * 1000 - (int)watch.ElapsedMilliseconds; if (timeToFinishFaultInjection > 0) { logger.Trace($"{nameof(FaultInjection)}: Waiting {timeToFinishFaultInjection}ms to ensure that FaultInjection duration passed."); await Task.Delay(timeToFinishFaultInjection).ConfigureAwait(false); } } }
private async Task ReceiveMessageUsingCallbackAndUnsubscribeAsync(TestDeviceType type, Client.TransportType transport) { TestDevice testDevice = await TestDevice.GetTestDeviceAsync(Logger, s_devicePrefix, type).ConfigureAwait(false); using DeviceClient deviceClient = testDevice.CreateDeviceClient(transport); using var testDeviceCallbackHandler = new TestDeviceCallbackHandler(deviceClient, testDevice, Logger); using var serviceClient = ServiceClient.CreateFromConnectionString(Configuration.IoTHub.ConnectionString); // For Mqtt - we will need to subscribe to the Mqtt receive telemetry topic // before the device can begin receiving c2d messages. if (transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { Client.Message leftoverMessage = await deviceClient.ReceiveAsync(s_fiveSeconds).ConfigureAwait(false); Logger.Trace($"Leftover message on Mqtt was: {leftoverMessage} with Id={leftoverMessage?.MessageId}"); } // First receive message using the polling ReceiveAsync() API. (Message firstMessage, _, _) = ComposeC2dTestMessage(Logger); await serviceClient.SendAsync(testDevice.Id, firstMessage).ConfigureAwait(false); Logger.Trace($"Sent C2D message from service, messageId={firstMessage.MessageId} - to be received on polling ReceiveAsync"); using Client.Message receivedFirstMessage = await deviceClient.ReceiveAsync(s_tenSeconds).ConfigureAwait(false); receivedFirstMessage.MessageId.Should().Be(firstMessage.MessageId); await deviceClient.CompleteAsync(receivedFirstMessage).ConfigureAwait(false); // Now, set a callback on the device client to receive C2D messages. await testDeviceCallbackHandler.SetMessageReceiveCallbackHandlerAsync().ConfigureAwait(false); // Now, send a message to the device from the service. (Message secondMessage, _, _) = ComposeC2dTestMessage(Logger); testDeviceCallbackHandler.ExpectedMessageSentByService = secondMessage; await serviceClient.SendAsync(testDevice.Id, secondMessage).ConfigureAwait(false); Logger.Trace($"Sent C2D message from service, messageId={secondMessage.MessageId} - to be received on callback"); // The message should be received on the callback, while a call to ReceiveAsync() should return null. using var cts = new CancellationTokenSource(s_tenSeconds); using Client.Message receivedSecondMessage = await deviceClient.ReceiveAsync(s_tenSeconds).ConfigureAwait(false); await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); receivedSecondMessage.Should().BeNull(); // Now unsubscribe from receiving c2d messages over the callback. await deviceClient.SetReceiveMessageHandlerAsync(null, deviceClient).ConfigureAwait(false); // For Mqtt - since we have explicitly unsubscribed, we will need to resubscribe again // before the device can begin receiving c2d messages. if (transport == Client.TransportType.Mqtt_Tcp_Only || transport == Client.TransportType.Mqtt_WebSocket_Only) { Client.Message leftoverMessage = await deviceClient.ReceiveAsync(s_fiveSeconds).ConfigureAwait(false); Logger.Trace($"Leftover message on Mqtt was: {leftoverMessage} with Id={leftoverMessage?.MessageId}"); } // Send a message to the device from the service. (Message thirdMessage, _, _) = ComposeC2dTestMessage(Logger); await serviceClient.SendAsync(testDevice.Id, thirdMessage).ConfigureAwait(false); Logger.Trace($"Sent C2D message from service, messageId={thirdMessage.MessageId} - to be received on polling ReceiveAsync"); // This time, the message should not be received on the callback, rather it should be received on a call to ReceiveAsync(). Func <Task> receiveMessageOverCallback = async() => { await testDeviceCallbackHandler.WaitForReceiveMessageCallbackAsync(cts.Token).ConfigureAwait(false); }; using Client.Message receivedThirdMessage = await deviceClient.ReceiveAsync(s_tenSeconds).ConfigureAwait(false); receivedThirdMessage.MessageId.Should().Be(thirdMessage.MessageId); receiveMessageOverCallback.Should().Throw <OperationCanceledException>(); await deviceClient.CompleteAsync(receivedThirdMessage).ConfigureAwait(false); firstMessage.Dispose(); secondMessage.Dispose(); thirdMessage.Dispose(); await deviceClient.CloseAsync().ConfigureAwait(false); await serviceClient.CloseAsync().ConfigureAwait(false); }