public async Task When_First_Join_Fails_Due_To_Slow_Twin_Update_Retry_Second_Attempt_Should_Succeed(string deviceGatewayID) { var simulatedDevice = new SimulatedDevice(TestDeviceInfo.CreateOTAADevice(1, gatewayID: deviceGatewayID)); var joinRequestPayload1 = simulatedDevice.CreateJoinRequest(); var joinRequestPayload2 = simulatedDevice.CreateJoinRequest(); var devAddr = (DevAddr?)null; var devEui = simulatedDevice.LoRaDevice.DevEui; // Device twin will be queried var twin = new Twin(); twin.Properties.Desired[TwinProperty.DevEUI] = devEui.ToString(); twin.Properties.Desired[TwinProperty.AppEui] = simulatedDevice.LoRaDevice.AppEui?.ToString(); twin.Properties.Desired[TwinProperty.AppKey] = simulatedDevice.LoRaDevice.AppKey?.ToString(); twin.Properties.Desired[TwinProperty.GatewayID] = deviceGatewayID; twin.Properties.Desired[TwinProperty.SensorDecoder] = simulatedDevice.LoRaDevice.SensorDecoder; LoRaDeviceClient.Setup(x => x.GetTwinAsync(CancellationToken.None)) .ReturnsAsync(twin); // Device twin will be updated AppSessionKey? afterJoin2AppSKey = null; NetworkSessionKey?afterJoin2NwkSKey = null; DevAddr? afterJoin2DevAddr = null; var mockSequence = new MockSequence(); LoRaDeviceClient.InSequence(mockSequence) .Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Returns <TwinCollection, CancellationToken>(async(_, token) => { try { await Task.Delay(TimeSpan.FromSeconds(20), token); Assert.True(false, "Token timeout expected"); } catch (OperationCanceledException) { } return(false); }); LoRaDeviceClient.InSequence(mockSequence) .Setup(x => x.UpdateReportedPropertiesAsync(It.IsNotNull <TwinCollection>(), It.IsAny <CancellationToken>())) .Returns <TwinCollection, CancellationToken>((updatedTwin, token) => { afterJoin2AppSKey = updatedTwin.SafeRead <AppSessionKey>(TwinProperty.AppSKey); afterJoin2NwkSKey = updatedTwin.SafeRead <NetworkSessionKey>(TwinProperty.NwkSKey); afterJoin2DevAddr = updatedTwin.SafeRead <DevAddr>(TwinProperty.DevAddr); return(Task.FromResult(true)); }); // Lora device api will be search by devices with matching deveui, LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload1.DevNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList())); LoRaDeviceApi.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, joinRequestPayload2.DevNonce)) .ReturnsAsync(new SearchDevicesResult(new IoTHubDeviceInfo(devAddr, devEui, "aabb").AsList())); using var cache = NewMemoryCache(); using var deviceRegistry = new LoRaDeviceRegistry(ServerConfiguration, cache, LoRaDeviceApi.Object, LoRaDeviceFactory, DeviceCache); using var messageProcessor = new MessageDispatcher( ServerConfiguration, deviceRegistry, FrameCounterUpdateStrategyProvider); using var joinRequest1 = CreateWaitableRequest(joinRequestPayload1, useRealTimer: true); messageProcessor.DispatchRequest(joinRequest1); await Task.Delay(TimeSpan.FromSeconds(7)); using var joinRequest2 = CreateWaitableRequest(joinRequestPayload2, useRealTimer: true); messageProcessor.DispatchRequest(joinRequest2); await Task.WhenAll(joinRequest1.WaitCompleteAsync(), joinRequest2.WaitCompleteAsync()); Assert.True(joinRequest1.ProcessingFailed); Assert.Null(joinRequest1.ResponseDownlink); Assert.Equal(LoRaDeviceRequestFailedReason.IoTHubProblem, joinRequest1.ProcessingFailedReason); Assert.True(joinRequest2.ProcessingSucceeded); Assert.NotNull(joinRequest2.ResponseDownlink); Assert.Single(DownstreamMessageSender.DownlinkMessages); Assert.True(DeviceCache.TryGetByDevEui(devEui, out var loRaDevice)); Assert.True(loRaDevice.IsOurDevice); Assert.Equal(afterJoin2DevAddr, loRaDevice.DevAddr); Assert.Equal(afterJoin2NwkSKey, loRaDevice.NwkSKey); Assert.Equal(afterJoin2AppSKey, loRaDevice.AppSKey); // get twin should happen only once LoRaDeviceClient.Verify(x => x.GetTwinAsync(CancellationToken.None), Times.Once()); LoRaDeviceClient.VerifyAll(); LoRaDeviceApi.VerifyAll(); }