using System; using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock; using ShareeBike.Model.Connector; using ShareeBike.Model.State; using ShareeBike.Repository.Request; using ShareeBike.Services.BluetoothLock; using ShareeBike.Services.BluetoothLock.Tdo; using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.OpenCommand; using ShareeBike.Services.BluetoothLock.Exception; using ShareeBike.Repository.Exception; using Newtonsoft.Json; using ShareeBike.Repository.Response; using ShareeBike.ViewModel; namespace SharedBusinessLogic.Tests.Model.Bikes.BikeInfoNS.BluetoothLock.Command { [TestFixture] public class TestOpenCommand { /// /// Use case: Open lock /// [Test] public async Task TestOpen() { var bike = Substitute.For(); var connector = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); locks[0].OpenAsync() .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success await InvokeAsync( bike, locks, () => true, // isConnectedDelegate (isConnected) => connector, listener, null); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.WaitStopPolling); listener.Received().ReportStep(Step.UpdateLockingState); await connector.Command.UpdateLockingStateAsync(bike); }); Assert.That( bike.LockInfo.State, Is.EqualTo(LockingState.Open)); } [Test] public void TestOpenOpenFailsOutOfReachException() { var bike = Substitute.For(); var connector = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); locks[0].OpenAsync() .Returns>(x => throw new OutOfReachException()); Assert.That( async () => await InvokeAsync( bike, locks, () => true, // isConnectedDelegate (isConnected) => connector, listener, null), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open await listener.ReportStateAsync(State.OutOfReachError, "Exception of type 'ShareeBike.Services.BluetoothLock.Exception.OutOfReachException' was thrown."); }); Assert.That( bike.LockInfo.State, Is.EqualTo(LockingState.UnknownDisconnected)); } [Test] public void TestOpenOpenFailsBoltStatusUnknownException() { var bike = Substitute.For(); var connector = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); locks[0].OpenAsync() .Returns>(x => throw new CouldntOpenBoldStatusIsUnknownException()); Assert.That( async () => await InvokeAsync( bike, locks, () => true, // isConnectedDelegate (isConnected) => connector, listener, null), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open await listener.ReportStateAsync(State.CouldntOpenBoldStatusIsUnknownError, "Position of lock bolt is unknown. Lock could be closed or open. Please try again or contact customer support."); listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.UpdateLockingState); }); Assert.That( bike.LockInfo.State, Is.EqualTo(LockingState.UnknownFromHardwareError)); } [Test] public void TestOpenOpenFailsCouldntOpenBoltBlockedException() { var bike = Substitute.For(); var connector = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); locks[0].OpenAsync() .Returns>(x => throw new CouldntOpenBoldIsBlockedException()); Assert.That( async () => await InvokeAsync(bike, locks, () => true, (isConnected) => connector, listener, null), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open await listener.ReportStateAsync(State.CouldntOpenBoldIsBlockedError, "Lock bolt is blocked. Make sure that no spoke presses against the lock bolt and try again."); listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.UpdateLockingState); }); Assert.That( bike.LockInfo.State, Is.EqualTo(LockingState.UnknownFromHardwareError)); } [Test] public void TestOpenOpenFailsCouldntOpenInconsistentStateException() { var bike = Substitute.For(); var connector = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); locks[0].OpenAsync() .Returns>(x => throw new CouldntOpenInconsistentStateExecption(LockitLockingState.Closed.GetLockingState())); Assert.That( async () => await InvokeAsync(bike, locks, () => true, (isConnected) => connector, listener, null), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open await listener.ReportStateAsync(State.CouldntOpenInconsistentStateError, "Unexpected locking state \"Closed\" detected after sending open command. Please try again or contact customer support."); listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.UpdateLockingState); }); Assert.That( bike.LockInfo.State, Is.EqualTo(LockingState.Closed)); } [Test] public void TestOpenOpenFailsException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); locks[0].OpenAsync() .Returns>(x => throw new Exception("Exception message.")); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); Assert.That( async () => await InvokeAsync(bike, locks, () => true, (isConnected) => connector, listener, null), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open await listener.ReportStateAsync(State.GeneralOpenError, "Exception message."); listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.UpdateLockingState); }); Assert.That( bike.LockInfo.State, Is.EqualTo(LockingState.UnknownDisconnected)); } [Test] public async Task TestOpenUpdateLockingStateFailsWebConnectFailure() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info.", new System.Exception("hoppla"))); await InvokeAsync(bike, locks, () => true, (isConnected) => connector, listener, null); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.WaitStopPolling); listener.Received().ReportStep(Step.UpdateLockingState); await listener.ReportStateAsync(State.WebConnectFailed, "Context info."); }); } [Test] public async Task TestOpenUpdateLockingStateFailsInvalidResponse() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message.")); await InvokeAsync(bike, locks, () => true, (isConnected) => connector, listener, null); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.WaitStopPolling); listener.Received().ReportStep(Step.UpdateLockingState); await listener.ReportStateAsync(State.ResponseIsInvalid, "Outer message."); }); } [Test] public async Task TestOpenUpdateLockingStateFails() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var locks = Substitute.For(); var listener = Substitute.For(); var viewUpdateManager = Substitute.For(); locks[0].OpenAsync() .Returns(LockitLockingState.Open); // Return lock state indicating success connector.Command.UpdateLockingStateAsync(bike).Returns(x => throw new Exception("Exception message.")); await InvokeAsync(bike, locks, () => true, (isConnected) => connector, listener, null); // Verify behavior Received.InOrder(async () => { listener.Received().ReportStep(Step.OpeningLock); await locks.Received()[0].OpenAsync(); // Lock must be open listener.Received().ReportStep(Step.GetLockInfos); listener.Received().ReportStep(Step.WaitStopPolling); listener.Received().ReportStep(Step.UpdateLockingState); await connector.Command.UpdateLockingStateAsync(bike); await listener.ReportStateAsync(State.BackendUpdateFailed, "Exception message."); }); } } }