using System; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using NSubstitute; using NUnit.Framework; using TINK.Model; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.State; using TINK.Model.User; using TINK.Repository.Exception; using TINK.Repository.Request; using TINK.Repository.Response; using TINK.Services.BluetoothLock; using TINK.Services.BluetoothLock.Exception; using TINK.Services.BluetoothLock.Tdo; using TINK.Services.Geolocation; using TINK.View; using TINK.ViewModel; using TINK.ViewModel.Bikes; using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand; namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler { [TestFixture] public class TestBookedOpen { /// /// Test construction of object. /// [Test] public void Testctor() { var handler = new BookedOpen( Substitute.For(), () => true, // isConnectedDelegate (isConnexted) => Substitute.For(), Substitute.For(), Substitute.For(), () => Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); // Verify state after action Assert.AreEqual("Close lock", handler.ButtonText); Assert.IsTrue(handler.IsButtonVisible); Assert.AreEqual(string.Empty, handler.LockitButtonText); Assert.That(handler.IsLockitButtonVisible, Is.False); } /// /// Use case: Close lock /// Final state: Booked closed. /// [Test] public void TestClose() { var bike = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => Substitute.For(), Substitute.For(), locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, Substitute.For()); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()).Returns(x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); handler.ReportStep(Step.UpdateLockingState); return Task.CompletedTask; } ); #else locks[0].CloseAsync() .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success #endif bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); // Return lock state indicating success var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked Closed" after action Assert.AreEqual("End rental", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("Open lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } /// /// Use case: Close lock /// [Test] public void TestCloseCloseFailsOutOfReachException() { var bike = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => Substitute.For(), Substitute.For(), locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, Substitute.For()); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()) .Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); await handler.ReportStateAsync(CloseCommand.State.OutOfReachError, ""); throw new OutOfReachException(); } ); #else locks[0].CloseAsync() .Returns>(x => throw new OutOfReachException()); #endif bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); /// If is fired lock state is set to state unknown because disconnected. var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert("Lock could not be closed!", "Make sure you have granted Bluetooth permission to the app. Step as close as possible to the bike lock and try again.", "OK"); bikesViewModel.ActionText = "Updating..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked disconnected" after action Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); Assert.IsFalse(subsequent.IsButtonVisible); Assert.AreEqual("Search lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } [Test] public void TestCloseCloseFailsCouldntCloseMovingException() { var bike = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => Substitute.For(), Substitute.For(), locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, Substitute.For()); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()) .Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); await handler.ReportStateAsync(CloseCommand.State.CouldntCloseMovingError, ""); throw new CouldntCloseMovingException(); } ); bike.LockInfo.State.Returns(LockingState.Open); // Locking state is open when CouldntCloseMovingException occurs. #else locks[0].CloseAsync() .Returns>(x => throw new CouldntCloseMovingException()); #endif bike.State.Value.Returns(InUseStateEnum.Booked); var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert("Lock could not be closed!", "The process is motion sensitive. Step close to the lock, do not move, and try again.", "OK"); bikesViewModel.ActionText = "Updating..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Close lock", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual(string.Empty, subsequent.LockitButtonText); Assert.That(subsequent.IsLockitButtonVisible, Is.False); } [Test] public void TestCloseCloseFailsCouldntCloseBoltBlockedException() { var bike = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => Substitute.For(), Substitute.For(), locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, Substitute.For()); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()) .Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); await handler.ReportStateAsync(CloseCommand.State.CouldntCloseBoltBlockedError, ""); throw new CouldntCloseBoltBlockedException(); } ); bike.LockInfo.State.Returns(LockingState.UnknownFromHardwareError); // Locking state is unknown when CouldntCloseBoltBlockedException occurs. #else locks[0].CloseAsync() .Returns>(x => throw new CouldntCloseBoltBlockedException()); #endif bike.State.Value.Returns(InUseStateEnum.Booked); var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert("Lock could not be closed!", "Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.", "OK"); bikesViewModel.ActionText = "Updating..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked disconnected" after action Assert.AreEqual("Open lock", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("Close lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } /// /// Use case: Close lock /// Final state: Same as initial state. /// [Test] public void TestCloseCloseFailsException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()).Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); await handler.ReportStateAsync(CloseCommand.State.GeneralCloseError, "Exception message."); throw new Exception("Exception message."); } ); bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // If CloseLock fires an exception of type Exception lock state is set to unknown. #else locks[0].CloseAsync() .Returns>(x => throw new Exception("Exception message.")); #endif bike.State.Value.Returns(InUseStateEnum.Booked); var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; #if USELOCALINSTANCE locks.Received()[0].CloseAsync(); // Lock must be closed #endif bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert( "Lock could not be closed!", "Exception message.", "OK" ); bikesViewModel.ActionText = "Updating..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked Disconnected" after action Assert.AreEqual("BookedDisconnected", subsequent.ButtonText); Assert.IsFalse(subsequent.IsButtonVisible); Assert.AreEqual("Search lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } /// /// Use case: Close lock /// Final state: Booked closed. /// [Test] public void TestCloseUpdateLockingStateFailsWebConnectFailureException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()).Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); handler.ReportStep(Step.UpdateLockingState); await handler.ReportStateAsync(CloseCommand.State.WebConnectFailed, "Context info"); } ); bike.LockInfo.State.Returns(LockingState.Closed); #else locks[0].CloseAsync() .Returns(LockitLockingState.Closed); // Return lock state indicating success connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info", new System.Exception("hoppla"))); #endif bike.State.Value.Returns(InUseStateEnum.Booked); var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; #if USELOCALINSTANCE locks.Received()[0].CloseAsync(); // Lock must be closed connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); #endif bikesViewModel.ActionText = "Internet must be available for updating lock status. Please establish an Internet connection!"; bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked Closed" after action Assert.AreEqual("End rental", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("Open lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } /// /// Use case: close lock /// Final state: Booked closed. /// [Test] public void TestCloseUpdateLockingStateFailsException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()) .Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); handler.ReportStep(Step.UpdateLockingState); await handler.ReportStateAsync(CloseCommand.State.BackendUpdateFailed, "Exception message."); } ); bike.LockInfo.State.Returns(LockingState.Closed); #else locks[0].CloseAsync() .Returns(LockitLockingState.Closed); // Return lock state indicating success connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new Exception("Exception message.")); #endif bike.State.Value.Returns(InUseStateEnum.Booked); var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; #if USELOCALINSTANCE locks.Received()[0].CloseAsync(); // Lock must be closed connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); #endif bikesViewModel.ActionText = "Connection error on updating locking status."; bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked Closed" after action Assert.AreEqual("End rental", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("Open lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } /// /// Use case: close lock /// Final state: Booked closed. /// [Test] public void TestCloseUpdateLockingStateFailsResponseException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new BookedOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); #if !USELOCALINSTANCE bike.CloseLockAsync(Arg.Any(), Arg.Any()) .Returns(async x => { // Add calls to ReportStep which IBikeInfoMutable implementation would do. handler.ReportStep(Step.ClosingLock); handler.ReportStep(Step.UpdateLockingState); await handler.ReportStateAsync(CloseCommand.State.ResponseIsInvalid, "Some invalid data received"); } ); bike.LockInfo.State.Returns(LockingState.Closed); #else locks[0].CloseAsync() .Returns(LockitLockingState.Closed); // Return lock state indicating success connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message.")); #endif bike.State.Value.Returns(InUseStateEnum.Booked); var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopAsync(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel."; #if USELOCALINSTANCE locks.Received()[0].CloseAsync(); // Lock must be closed connector.Command.UpdateLockingStateAsync(bike, Arg.Any()); #endif bikesViewModel.ActionText = "Status error on updating lock state."; bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state "Booked Closed" after action Assert.AreEqual("End rental", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("Open lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } } }