Connection manager tests
This commit is contained in:
Родитель
c87be02c7a
Коммит
5ae087d68a
|
@ -1,3 +1,4 @@
|
|||
#Junk Files
|
||||
*.DS_Store
|
||||
[Tt]humbs.db
|
||||
|
||||
|
@ -34,6 +35,7 @@ ipch/
|
|||
*.bak.*
|
||||
*.bak
|
||||
.vs/
|
||||
FakesAssemblies/
|
||||
|
||||
#Tools
|
||||
_ReSharper.*
|
||||
|
|
|
@ -418,7 +418,7 @@
|
|||
<member name="M:DeviceBridge.Services.ConnectionManager.GetDeviceStatus(System.String)">
|
||||
<summary>
|
||||
See <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.devices.client.connectionstatus?view=azure-dotnet">ConnectionStatus documentation</see>
|
||||
for a detailed description of each status and reaosn.
|
||||
for a detailed description of each status and reason.
|
||||
</summary>
|
||||
<param name="deviceId">Id of the device to get the status for.</param>
|
||||
<returns>The last known connection status of the device or null if the device has never connected.</returns>
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace DeviceBridge.Services
|
|||
|
||||
/// <summary>
|
||||
/// See <see href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.devices.client.connectionstatus?view=azure-dotnet">ConnectionStatus documentation</see>
|
||||
/// for a detailed description of each status and reaosn.
|
||||
/// for a detailed description of each status and reason.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">Id of the device to get the status for.</param>
|
||||
/// <returns>The last known connection status of the device or null if the device has never connected.</returns>
|
||||
|
|
|
@ -8,6 +8,15 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="FakesAssemblies\**" />
|
||||
<EmbeddedResource Remove="FakesAssemblies\**" />
|
||||
<None Remove="FakesAssemblies\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.33.1" />
|
||||
<PackageReference Include="Microsoft.Azure.Devices.Provisioning.Client" Version="1.6.0" />
|
||||
<PackageReference Include="Microsoft.QualityTools.Testing.Fakes" Version="16.7.4-beta.20330.2" />
|
||||
<PackageReference Include="Moq" Version="4.15.2" />
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||
|
@ -26,4 +35,8 @@
|
|||
<AdditionalFiles Include="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalDesignTimeBuildInput Remove="FakesAssemblies\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Двоичный файл не отображается.
Двоичные данные
DeviceBridgeTests/Fakes/Microsoft.Azure.Devices.Provisioning.Client.fakes
Normal file
Двоичные данные
DeviceBridgeTests/Fakes/Microsoft.Azure.Devices.Provisioning.Client.fakes
Normal file
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace DeviceBridge.Services.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ConnectionManagerServiceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task TryGetDeviceClient()
|
||||
{
|
||||
// TODO
|
||||
// Returns device client if one exists
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InitializeDeviceClientAsync()
|
||||
{
|
||||
// TODO
|
||||
// Multiple calls to InitializeDeviceClientAsync do not run in parallel (test mutual exclusion)
|
||||
// Calls to InitializeDeviceClientAsync and TearDownDeviceClientAsync do not run in parallel (test mutual exclusion)
|
||||
// Returns existing client if one already exists
|
||||
// Tries to connect to cached device hub, if one exists, before attempting other known hubs
|
||||
// Tries to connect to all known hubs before trying DPS registration
|
||||
// Fails right away if OpenAsync throws an exception not in the list of expected errors
|
||||
// Tries DPS registration with correct key if attempts to connect to all known hubs fail
|
||||
// Adds new hub to local device -> hub cache
|
||||
// Tries to add new hub to local cache of known hubs
|
||||
// Tries to store new hub in Key Vault if it was not yet stored
|
||||
// Sets pooling and correct pool size when building a client
|
||||
// Sets custom retry policy when building a client
|
||||
// Disposes temporary client if an error happens during client build
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TearDownDeviceClientAsync()
|
||||
{
|
||||
// TODO
|
||||
// Multiple calls to TearDownDeviceClientAsync do not run in parallel (test mutual exclusion)
|
||||
// Calls to InitializeDeviceClientAsync and TearDownDeviceClientAsync do not run in parallel (test mutual exclusion)
|
||||
// Removes client from list before closing
|
||||
// Calls CloseAsync before disposing
|
||||
// Disposes client
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ConnectionStatusChange()
|
||||
{
|
||||
// TODO
|
||||
// SDK connection status changes update device status
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,563 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DeviceBridge.Common.Exceptions;
|
||||
using DeviceBridge.Models;
|
||||
using DeviceBridge.Providers;
|
||||
using Microsoft.Azure.Devices.Client;
|
||||
using Microsoft.Azure.Devices.Client.Exceptions;
|
||||
using Microsoft.Azure.Devices.Provisioning.Client;
|
||||
using Microsoft.QualityTools.Testing.Fakes;
|
||||
using Moq;
|
||||
using NLog;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace DeviceBridge.Services.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ConnectionManagerTests
|
||||
{
|
||||
private Mock<IStorageProvider> _storageProviderMock = new Mock<IStorageProvider>();
|
||||
|
||||
[SetUp]
|
||||
public async Task Setup()
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncMutualExclusion()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
var connectionManager = CreateConnectionManager();
|
||||
|
||||
// Check that client open and close operations for the same device block on the same mutex.
|
||||
SemaphoreSlim openSemaphore = null, closeSemaphore = null;
|
||||
CaptureSemaphoreOnWait((semaphore) => openSemaphore = semaphore);
|
||||
ShimDps("test-hub.azure.devices.net");
|
||||
ShimDeviceClient();
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
CaptureSemaphoreOnWait((semaphore) => closeSemaphore = semaphore);
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("test-device-id");
|
||||
Assert.IsNotNull(openSemaphore);
|
||||
Assert.AreEqual(openSemaphore, closeSemaphore);
|
||||
|
||||
// Check that client open operations for different devices block on different mutexes.
|
||||
SemaphoreSlim anotherDeviceOpenSemaphore = null;
|
||||
CaptureSemaphoreOnWait((semaphore) => anotherDeviceOpenSemaphore = semaphore);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("another-test-device-id");
|
||||
Assert.IsNotNull(anotherDeviceOpenSemaphore);
|
||||
Assert.AreNotEqual(openSemaphore, anotherDeviceOpenSemaphore);
|
||||
|
||||
// Check that the mutex is unlocked on failure
|
||||
ShimDeviceClientToFail();
|
||||
SemaphoreSlim openFailSemaphore = null;
|
||||
CaptureSemaphoreOnWait((semaphore) => openFailSemaphore = semaphore);
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("device-to-fail-id"));
|
||||
Assert.AreEqual(openFailSemaphore.CurrentCount, 1);
|
||||
|
||||
// Check that a device connection attempt time is registered before it enters the critical section.
|
||||
var startTime = DateTime.Now;
|
||||
SemaphoreSlim connectionTimeSemaphore = null;
|
||||
ShimDeviceClient();
|
||||
CaptureSemaphoreOnWait((semaphore) =>
|
||||
{
|
||||
connectionTimeSemaphore = semaphore;
|
||||
Assert.IsNotNull(connectionManager.GetDevicesThatConnectedSince(startTime).Find(id => id == "connection-time-test-id"));
|
||||
});
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("connection-time-test-id");
|
||||
Assert.NotNull(connectionTimeSemaphore);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncTemporaryVsPermanent()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
var connectionManager = CreateConnectionManager();
|
||||
int closeCount = 0;
|
||||
|
||||
// If temporary is set to false (default), creates a permanent connection without creating or renewing a temporary connection.
|
||||
ShimDps("test-hub.azure.devices.net");
|
||||
ShimDeviceClientAndCaptureClose(() => closeCount++);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("permanent-device-id");
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("permanent-device-id", true);
|
||||
Assert.AreEqual(closeCount, 0, "Closing a temporary connection should not have closed a permanent connection");
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("permanent-device-id");
|
||||
Assert.AreEqual(closeCount, 1);
|
||||
|
||||
// If temporary is set to true, creates a temporary connection if one doesn't exist, without creating a permanent connection.
|
||||
closeCount = 0;
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("temporary-device-id", true);
|
||||
ShimUtcNowAhead(20); // Move the clock so the temporary connection will expire.
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("temporary-device-id");
|
||||
Assert.AreEqual(closeCount, 0, "Closing a permanent connection should not have closed a temporary connection");
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("temporary-device-id", true);
|
||||
Assert.AreEqual(closeCount, 1);
|
||||
|
||||
// If temporary is set to true, renews a temporary connection if one already exists, without creating a permanent connection.
|
||||
closeCount = 0;
|
||||
UnshimUtcNow();
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("renew-device-id", true); // Create initial ~10min connection.
|
||||
ShimUtcNowAhead(5);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("renew-device-id", true); // Move the clock 5min and renew connection for another ~10min, so total connection duration is ~15min.
|
||||
ShimUtcNowAhead(12);
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("renew-device-id", true);
|
||||
Assert.AreEqual(closeCount, 0, "Temporary connection should not have been closed after 12min, as it was renewed for ~15min");
|
||||
ShimUtcNowAhead(18);
|
||||
await connectionManager.AssertDeviceConnectionClosedAsync("renew-device-id", true);
|
||||
Assert.AreEqual(closeCount, 1, "Temporary connection should have been closed after 18min.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncRecreateFailedClient()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
var connectionManager = CreateConnectionManager();
|
||||
int closeCount = 0;
|
||||
|
||||
// Create a client that instantly goes to a failure state.
|
||||
ShimDps("test-hub.azure.devices.net");
|
||||
ShimDeviceClientAndEmitStatus(ConnectionStatus.Disconnected, ConnectionStatusChangeReason.Retry_Expired);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("recreate-failed-device-id");
|
||||
|
||||
// If recreateFailedClient is set to false (default), don't try to recreate a client in a permanent failure state
|
||||
ShimDeviceClientAndCaptureClose(() => closeCount++);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("recreate-failed-device-id");
|
||||
Assert.AreEqual(closeCount, 0);
|
||||
|
||||
// If recreateFailedClient is set to true, tries to recreate a client in a permanent failure state
|
||||
ShimDeviceClientAndCaptureClose(() => closeCount++);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("recreate-failed-device-id", false, true);
|
||||
Assert.AreEqual(closeCount, 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncTriesCachedHub()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
var hubCache = new List<HubCacheEntry>()
|
||||
{
|
||||
new HubCacheEntry()
|
||||
{
|
||||
DeviceId = "test-device-id",
|
||||
Hub = "known-hub.azure.devices.net",
|
||||
},
|
||||
};
|
||||
var connectionManager = CreateConnectionManager(hubCache);
|
||||
|
||||
// Check that it Attempts to connect to the cached device hub first, if one exists.
|
||||
string connStr = null;
|
||||
ShimDeviceClientAndCaptureConnectionString(capturedConnStr => connStr = capturedConnStr);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
StringAssert.Contains("known-hub.azure.devices.net", connStr);
|
||||
|
||||
// Check that the device client is cached and not reopened in subsequent calls.
|
||||
bool openAttempted = false;
|
||||
ShimDeviceClientAndCaptureOpen(() => openAttempted = true);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
Assert.False(openAttempted);
|
||||
|
||||
// Check that DPS registration is eventually attempted if connection error indicates that the device doesn't exist in the target hub.
|
||||
connectionManager = CreateConnectionManager(hubCache);
|
||||
ShimDeviceClientToFail(new DeviceNotFoundException());
|
||||
var registrationAttempted = false;
|
||||
ShimDpsAndCaptureRegistration("test-hub.azure.devices.net", () => registrationAttempted = true);
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("test-device-id"));
|
||||
Assert.True(registrationAttempted);
|
||||
|
||||
// Check that DPS registration is not attempted if connection attempt fails with unknown error.
|
||||
registrationAttempted = false;
|
||||
ShimDeviceClientToFail(new Exception());
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("test-device-id"));
|
||||
Assert.False(registrationAttempted);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncTriesAllKnownHubs()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
var hubCache = new List<HubCacheEntry>()
|
||||
{
|
||||
new HubCacheEntry()
|
||||
{
|
||||
DeviceId = "another-device-id-1",
|
||||
Hub = "known-hub-1.azure.devices.net",
|
||||
},
|
||||
new HubCacheEntry()
|
||||
{
|
||||
DeviceId = "another-device-id-2",
|
||||
Hub = "known-hub-2.azure.devices.net",
|
||||
},
|
||||
};
|
||||
var connectionManager = CreateConnectionManager(hubCache);
|
||||
|
||||
// Check that it Attempts to connect to a known hub, even if it the device Id doesn't match.
|
||||
string connStr = null;
|
||||
ShimDeviceClientAndCaptureConnectionString(capturedConnStr => connStr = capturedConnStr);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
Assert.True(connStr.Contains("known-hub-1.azure.devices.net") || connStr.Contains("known-hub-2.azure.devices.net"));
|
||||
|
||||
// Check that hub was cached in the DB.
|
||||
_storageProviderMock.Verify(p => p.AddOrUpdateHubCacheEntry(It.IsAny<Logger>(), "test-device-id", It.IsIn(new string[] { "known-hub-1.azure.devices.net", "known-hub-2.azure.devices.net" })), Times.Once);
|
||||
|
||||
// Checks that failure to save hub in DB cache doesn't fail the open operation.
|
||||
connectionManager = CreateConnectionManager(hubCache);
|
||||
_storageProviderMock.Setup(p => p.AddOrUpdateHubCacheEntry(It.IsAny<Logger>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new Exception());
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
_storageProviderMock.Setup(p => p.AddOrUpdateHubCacheEntry(It.IsAny<Logger>(), It.IsAny<string>(), It.IsAny<string>())).Verifiable();
|
||||
|
||||
// Check that the device client is cached and not reopened in subsequent calls.
|
||||
bool openAttempted = false;
|
||||
ShimDeviceClientAndCaptureOpen(() => openAttempted = true);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
Assert.False(openAttempted);
|
||||
|
||||
// Check that all hubs are tried and DPS registration is eventually attempted if connection
|
||||
// error indicates that the device doesn't exist in the target hub.
|
||||
connectionManager = CreateConnectionManager(hubCache);
|
||||
var connStrs = new List<string>();
|
||||
ShimDeviceClientToFailAndCaptureConnectionString(capturedConnStr => connStrs.Add(capturedConnStr), new DeviceNotFoundException());
|
||||
var registrationAttempted = false;
|
||||
ShimDpsAndCaptureRegistration("test-hub.azure.devices.net", () => registrationAttempted = true);
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("test-device-id"));
|
||||
Assert.True((connStrs.Find(s => s.Contains("known-hub-1.azure.devices.net")) != null) && (connStrs.Find(s => s.Contains("known-hub-2.azure.devices.net")) != null));
|
||||
Assert.True(registrationAttempted);
|
||||
|
||||
// Check that DPS registration is not attempted if connection attempt fails with unknown error.
|
||||
registrationAttempted = false;
|
||||
ShimDeviceClientToFail(new Exception());
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("test-device-id"));
|
||||
Assert.False(registrationAttempted);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncDps()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
var connectionManager = CreateConnectionManager();
|
||||
|
||||
// Checks that it attempts to connect to the hub returned by DPS.
|
||||
string connStr = null;
|
||||
ShimDps("test-hub.azure.devices.net");
|
||||
ShimDeviceClientAndCaptureConnectionString(capturedConnStr => connStr = capturedConnStr);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("dps-test-device-id");
|
||||
Assert.True(connStr.Contains("test-hub.azure.devices.net"));
|
||||
|
||||
// Check that the hub returned by DPS was cached in the DB.
|
||||
_storageProviderMock.Verify(p => p.AddOrUpdateHubCacheEntry(It.IsAny<Logger>(), "dps-test-device-id", "test-hub.azure.devices.net"), Times.Once);
|
||||
|
||||
// Checks that failure to save hub in DB cache doesn't fail the open operation.
|
||||
connectionManager = CreateConnectionManager();
|
||||
_storageProviderMock.Setup(p => p.AddOrUpdateHubCacheEntry(It.IsAny<Logger>(), It.IsAny<string>(), It.IsAny<string>())).Throws(new Exception());
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("dps-test-device-id");
|
||||
_storageProviderMock.Setup(p => p.AddOrUpdateHubCacheEntry(It.IsAny<Logger>(), It.IsAny<string>(), It.IsAny<string>())).Verifiable();
|
||||
|
||||
// Check that the device client is cached and not reopened in subsequent calls.
|
||||
bool openAttempted = false;
|
||||
ShimDeviceClientAndCaptureOpen(() => openAttempted = true);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("dps-test-device-id");
|
||||
Assert.False(openAttempted);
|
||||
|
||||
// Operation fails if DPS registration fails.
|
||||
connectionManager = CreateConnectionManager();
|
||||
ShimDpsToFail();
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("dps-test-device-id"));
|
||||
|
||||
// Fails with DpsRegistrationFailedWithUnknownStatusException if DPS returns unknown response.
|
||||
ShimDps("test-hub.azure.devices.net", ProvisioningRegistrationStatusType.Failed);
|
||||
await ExpectToThrow(() => connectionManager.AssertDeviceConnectionOpenAsync("dps-test-device-id"), e => e is DpsRegistrationFailedWithUnknownStatusException);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssertDeviceConnectionOpenAsyncClient()
|
||||
{
|
||||
using (ShimsContext.Create())
|
||||
{
|
||||
// Uses correct connection string.
|
||||
var connectionManager = CreateConnectionManager();
|
||||
string connStr = null;
|
||||
ShimDps("test-hub.azure.devices.net");
|
||||
ShimDeviceClientAndCaptureConnectionString(capturedConnStr => connStr = capturedConnStr);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes("test-sas-key")))
|
||||
{
|
||||
var derivedKey = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes("test-device-id")));
|
||||
Assert.AreEqual(connStr, $"HostName=test-hub.azure.devices.net;DeviceId=test-device-id;SharedAccessKey={derivedKey}");
|
||||
}
|
||||
|
||||
// Sets connection status change handler that updates local device connection status and calls user-defined
|
||||
// callback if one exists and we're not in hub-probing phase.
|
||||
connectionManager = CreateConnectionManager();
|
||||
var statusCallbackCalled = false;
|
||||
connectionManager.SetConnectionStatusCallback("test-device-id", (_, __) => Task.FromResult(statusCallbackCalled = true));
|
||||
ShimDeviceClientAndEmitStatus(ConnectionStatus.Connected, ConnectionStatusChangeReason.Connection_Ok);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
Assert.True(statusCallbackCalled);
|
||||
var status = connectionManager.GetDeviceStatus("test-device-id");
|
||||
Assert.AreEqual(status?.status, ConnectionStatus.Connected);
|
||||
Assert.AreEqual(status?.reason, ConnectionStatusChangeReason.Connection_Ok);
|
||||
|
||||
// Correctly sets desired property update, methods, and C2D message callbacks if they exist.
|
||||
connectionManager = CreateConnectionManager();
|
||||
bool desiredPropertyCallbackCalled = false, methodCallbackCalled = false, c2dCallbackCalled = false;
|
||||
await connectionManager.SetMethodCallbackAsync("test-device-id", "", (_, __) => {
|
||||
methodCallbackCalled = true;
|
||||
return Task.FromResult(new MethodResponse(200));
|
||||
});
|
||||
await connectionManager.SetMessageCallbackAsync("test-device-id", "", (_) =>
|
||||
{
|
||||
c2dCallbackCalled = true;
|
||||
return Task.FromResult(ReceiveMessageCallbackStatus.Accept);
|
||||
});
|
||||
await connectionManager.SetDesiredPropertyUpdateCallbackAsync("test-device-id", "", (_, __) => Task.FromResult(desiredPropertyCallbackCalled = true));
|
||||
MethodCallback capturedMethodCallback = null;
|
||||
ReceiveMessageCallback capturedMessageCallback = null;
|
||||
DesiredPropertyUpdateCallback capturedPropertyUpdateCallback = null;
|
||||
ShimDeviceClientAndCaptureAllHandlers(handler => capturedMethodCallback = handler, handler => capturedMessageCallback = handler, handler => capturedPropertyUpdateCallback = handler);
|
||||
await connectionManager.AssertDeviceConnectionOpenAsync("test-device-id");
|
||||
await capturedMethodCallback(null, null);
|
||||
await capturedMessageCallback(null, null);
|
||||
await capturedPropertyUpdateCallback(null, null);
|
||||
Assert.True(desiredPropertyCallbackCalled);
|
||||
Assert.True(methodCallbackCalled);
|
||||
Assert.True(c2dCallbackCalled);
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectionManager CreateConnectionManager(List<HubCacheEntry> hubCache = null)
|
||||
{
|
||||
_storageProviderMock.Setup(p => p.ListHubCacheEntries(It.IsAny<Logger>())).Returns(Task.FromResult(hubCache ?? new List<HubCacheEntry>()));
|
||||
return new ConnectionManager(LogManager.GetCurrentClassLogger(), "test-id-scope", Convert.ToBase64String(Encoding.UTF8.GetBytes("test-sas-key")), 50, _storageProviderMock.Object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims SemaphoreSlime to capture the target semaphore of WaitAsync.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="onCapture">Delegate called when semaphore is captured.</param>
|
||||
private static void CaptureSemaphoreOnWait(Action<SemaphoreSlim> onCapture)
|
||||
{
|
||||
System.Threading.Fakes.ShimSemaphoreSlim.AllInstances.WaitAsync = (@this) =>
|
||||
{
|
||||
onCapture(@this);
|
||||
return ShimsContext.ExecuteWithoutShims(() => @this.WaitAsync());
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims DPS registration to return a successful assignment.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="hubToAssign">Hub to be returned in the assignment.</param>
|
||||
private static void ShimDps(string hubToAssign, ProvisioningRegistrationStatusType? status = null)
|
||||
{
|
||||
Microsoft.Azure.Devices.Provisioning.Client.Fakes.ShimProvisioningDeviceClient.AllInstances.RegisterAsync = (@this) =>
|
||||
Task.FromResult(new DeviceRegistrationResult("some-id", DateTime.Now, hubToAssign, "some-id", status ?? ProvisioningRegistrationStatusType.Assigned, "", DateTime.Now, 0, "", ""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims DPS registration to fail.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
private static void ShimDpsToFail()
|
||||
{
|
||||
Microsoft.Azure.Devices.Provisioning.Client.Fakes.ShimProvisioningDeviceClient.AllInstances.RegisterAsync = (@this) => throw new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims DPS registration to return a successful assignment and captures the registration call.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="hubToAssign">Hub to be returned in the assignment.</param>
|
||||
/// <param name="onRegister">Action to execute on registration.</param>
|
||||
private static void ShimDpsAndCaptureRegistration(string hubToAssign, Action onRegister)
|
||||
{
|
||||
Microsoft.Azure.Devices.Provisioning.Client.Fakes.ShimProvisioningDeviceClient.AllInstances.RegisterAsync = (@this) =>
|
||||
{
|
||||
onRegister();
|
||||
return Task.FromResult(new DeviceRegistrationResult("some-id", DateTime.Now, hubToAssign, "some-id", ProvisioningRegistrationStatusType.Assigned, "", DateTime.Now, 0, "", ""));
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the DeviceClient to return success in all calls.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
private static void ShimDeviceClient()
|
||||
{
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.CreateFromConnectionStringStringITransportSettingsArrayClientOptions = (string connStr, ITransportSettings[] settings, ClientOptions _) => ShimsContext.ExecuteWithoutShims(() => DeviceClient.CreateFromConnectionString(connStr, settings));
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.OpenAsync = (@this) => Task.CompletedTask;
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.CloseAsync = (@this) => Task.CompletedTask;
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.CompleteAsyncMessage = (@this, message) => Task.CompletedTask;
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.RejectAsyncMessage = (@this, message) => Task.CompletedTask;
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.AbandonAsyncMessage = (@this, message) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the DeviceClient to return success in all calls and captures open call.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="onOpen">Delegate to be called when OpenAsync is called.</param>
|
||||
private static void ShimDeviceClientAndCaptureOpen(Action onOpen)
|
||||
{
|
||||
ShimDeviceClient();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.OpenAsync = (@this) =>
|
||||
{
|
||||
onOpen();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the DeviceClient to return success in all calls and captures close call.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="onClose">Delegate to be called when CloseAsync is called.</param>
|
||||
private static void ShimDeviceClientAndCaptureClose(Action onClose)
|
||||
{
|
||||
ShimDeviceClient();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.CloseAsync = (@this) =>
|
||||
{
|
||||
onClose();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the DeviceClient to emit a specific status when the status change handler is registered.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="status">status.</param>
|
||||
/// <param name="reason">status reason.</param>
|
||||
private static void ShimDeviceClientAndEmitStatus(ConnectionStatus status, ConnectionStatusChangeReason reason)
|
||||
{
|
||||
ShimDeviceClient();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.SetConnectionStatusChangesHandlerConnectionStatusChangesHandler = (@this, handler) =>
|
||||
{
|
||||
if (handler != null)
|
||||
{
|
||||
handler(status, reason);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void ShimDeviceClientAndCaptureAllHandlers(Action<MethodCallback> onMethodHandlerCaptured, Action<ReceiveMessageCallback> onMessageHandlerCaptured, Action<DesiredPropertyUpdateCallback> onDesiredPropertyHandlerCaptured)
|
||||
{
|
||||
ShimDeviceClient();
|
||||
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.SetMethodDefaultHandlerAsyncMethodCallbackObject = (@this, handler, context) =>
|
||||
{
|
||||
onMethodHandlerCaptured(handler);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.SetReceiveMessageHandlerAsyncReceiveMessageCallbackObjectCancellationToken = (@this, handler, context, token) =>
|
||||
{
|
||||
onMessageHandlerCaptured(handler);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.SetDesiredPropertyUpdateCallbackAsyncDesiredPropertyUpdateCallbackObject = (@this, handler, context) =>
|
||||
{
|
||||
onDesiredPropertyHandlerCaptured(handler);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the device client and capture the connection string used to create it.
|
||||
/// </summary>
|
||||
/// /// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="onCreate">Action to execute when connection string is captured.</param>
|
||||
private static void ShimDeviceClientAndCaptureConnectionString(Action<string> onCreate)
|
||||
{
|
||||
ShimDeviceClient();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.CreateFromConnectionStringStringITransportSettingsArrayClientOptions = (string connStr, ITransportSettings[] settings, ClientOptions _) => {
|
||||
onCreate(connStr);
|
||||
return ShimsContext.ExecuteWithoutShims(() => DeviceClient.CreateFromConnectionString(connStr, settings));
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the device client to fail and capture the connection string used to create it.
|
||||
/// </summary>
|
||||
/// /// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="onCreate">Action to execute when connection string is captured.</param>
|
||||
/// /// <param name="exception">Exception to throw.</param>
|
||||
private static void ShimDeviceClientToFailAndCaptureConnectionString(Action<string> onCreate, Exception exception = null)
|
||||
{
|
||||
ShimDeviceClientToFail();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.CreateFromConnectionStringStringITransportSettingsArrayClientOptions = (string connStr, ITransportSettings[] settings, ClientOptions _) => {
|
||||
onCreate(connStr);
|
||||
throw exception ?? new Exception();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims the DeviceClient to fail in all calls.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="exception">Exception to throw.</param>
|
||||
private static void ShimDeviceClientToFail(Exception exception = null)
|
||||
{
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.CreateFromConnectionStringStringITransportSettingsArrayClientOptions = (string connStr, ITransportSettings[] settings, ClientOptions _) => throw exception ?? new Exception();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.OpenAsync = (@this) => throw exception ?? new Exception();
|
||||
Microsoft.Azure.Devices.Client.Fakes.ShimDeviceClient.AllInstances.CloseAsync = (@this) => throw exception ?? new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that an async function throws.
|
||||
/// </summary>
|
||||
/// <param name="fn">The async function to await.</param>
|
||||
private static async Task ExpectToThrow(Func<Task> fn, Func<Exception, bool> exceptionTest = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await fn();
|
||||
Assert.Fail("Expected function to throw");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (exceptionTest != null && !exceptionTest(e))
|
||||
{
|
||||
Assert.Fail("Exception didn't match test");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shims UtcNow to return a specific number of minutes into the future.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
/// <param name="minutes">How much to move the original time ahead.</param>
|
||||
private static void ShimUtcNowAhead(int minutes)
|
||||
{
|
||||
System.Fakes.ShimDateTimeOffset.UtcNowGet = () => ShimsContext.ExecuteWithoutShims(() => DateTimeOffset.UtcNow).AddMinutes(minutes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverts UtcNow to its original behavior.
|
||||
/// </summary>
|
||||
/// <remarks>Must be used within a ShimsContext.</remarks>
|
||||
private static void UnshimUtcNow()
|
||||
{
|
||||
System.Fakes.ShimDateTimeOffset.UtcNowGet = () => ShimsContext.ExecuteWithoutShims(() => DateTimeOffset.UtcNow);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче