using System; using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.State; using TINK.Model.User; using TINK.Repository.Exception; 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; namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler { [TestFixture] public class TestDisposableOpen { /// /// Test construction of object. /// [Test] public void Testctor() { var handler = new DisposableOpen( Substitute.For(), () => true, // isConnectedDelegate (isConnexted) => Substitute.For(), Substitute.For(), Substitute.For(), () => Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); // Verify prerequisites. Assert.AreEqual("Rent bike or close lock", handler.ButtonText); Assert.IsTrue(handler.IsButtonVisible); Assert.AreEqual(nameof(DisposableOpen), handler.LockitButtonText); Assert.IsFalse(handler.IsLockitButtonVisible); } /// /// Use case: Lock bike. /// Final state: Disposable Closed. /// [Test] public void TestCloseLock() { 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 DisposableOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(false)); locks[0].CloseAsync().Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. var subsequent = handler.HandleRequestOption1().Result; // Verify behaviour Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "

Lock is closing.
Please wait until it is completely closed.

"; locks[0].CloseAsync(); bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); bikesViewModel.ActionText = "Updating..."; pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again bikesViewModel.ActionText = ""; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Reserve bike", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); Assert.IsFalse(subsequent.IsLockitButtonVisible); } /// /// Use case: Lock bike. /// Final state: Same as initial state. /// [Test] public void TestCloseLockCloseFailsOutOfReachExcption() { 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 DisposableOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(false)); locks[0].CloseAsync().Returns>(x => throw new OutOfReachException()); bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. bike.LockInfo.State.Returns(LockingState.Open); var subsequent = handler.HandleRequestOption1().Result; locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); // Verify behaviour Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "

Lock is closing.
Please wait until it is completely closed.

"; locks[0].CloseAsync(); bikesViewModel.ActionText = ""; viewService.DisplayAlert("Lock can not be closed!", "Lock cannot be closed until bike is near.", "OK"); bikesViewModel.ActionText = "Updating..."; pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again bikesViewModel.ActionText = ""; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Reserve bike", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual(nameof(DisposableDisconnected), subsequent.LockitButtonText); Assert.IsFalse(subsequent.IsLockitButtonVisible); } /// /// Use case: Lock bike. /// Final state: Same as initial state. /// [Test] public void TestCloseLockCloseFailsExcption() { 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 DisposableOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(false)); locks[0].CloseAsync().Returns>(x => throw new Exception("Exception message.")); bike.State.Value.Returns(InUseStateEnum.Disposable); // Reqesthandler factory queries state to create appropriate request handler object. bike.LockInfo.State.Returns(LockingState.Open); var subsequent = handler.HandleRequestOption1().Result; locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); // Verify behaviour Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "

Lock is closing.
Please wait until it is completely closed.

"; locks[0].CloseAsync(); bikesViewModel.ActionText = ""; viewService.DisplayAlert("Lock can not be closed!", "Exception message.", "OK"); bikesViewModel.ActionText = "Updating..."; pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again bikesViewModel.ActionText = ""; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Reserve bike", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual(nameof(DisposableDisconnected), subsequent.LockitButtonText); Assert.IsFalse(subsequent.IsLockitButtonVisible); } /// /// Use case: Lock bike. /// Final state: Booked Open. /// [Test] public void TestDoBook() { 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 DisposableOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(true)); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Open); var subsequent = handler.HandleRequestOption1().Result; // Verify behaviour Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "Reading charging level..."; locks[0].GetBatteryPercentageAsync(); bikesViewModel.ActionText = "Renting bike..."; connector.Command.DoBook(bike); bikesViewModel.ActionText = "Updating..."; pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again bikesViewModel.ActionText = ""; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Close lock & return bike", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("Close lock", subsequent.LockitButtonText); Assert.IsTrue(subsequent.IsLockitButtonVisible); } /// /// Use case: Lock bike. /// Final state: Disposabled Closed. /// [Test] public void TestDoBookDoBookFailsWebConnectFailureException() { 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 DisposableOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(true)); connector.Command.DoBook(bike).Returns(x => throw new WebConnectFailureException("Context info.", new Exception("chub"))); locks[0].CloseAsync().Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); bike.State.Value.Returns(InUseStateEnum.Disposable); var subsequent = handler.HandleRequestOption1().Result; // Verify behaviour Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "Reading charging level..."; locks[0].GetBatteryPercentageAsync(); bikesViewModel.ActionText = "Renting bike..."; connector.Command.DoBook(bike); bikesViewModel.ActionText = ""; viewService.DisplayAlert( "Connection error when renting the bike!", string.Format("Attention: Lock is closed!\r\n{0}\r\n{1}", "Context info.", "Is WIFI available/ mobile network available and mobile data activated / ... ?"), "OK"); bikesViewModel.ActionText = "Verschließe Schloss..."; locks[0].CloseAsync(); bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); bikesViewModel.ActionText = "Updating..."; pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again bikesViewModel.ActionText = ""; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Reserve bike", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); Assert.IsFalse(subsequent.IsLockitButtonVisible); } /// /// Use case: Lock bike. /// Final state: Disposabled Closed. /// [Test] public void TestDoBookDoBookFailsException() { 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 DisposableOpen( bike, () => true, // isConnectedDelegate (isConnexted) => connector, geolocation, locks, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert(string.Empty, "Fahrrad Nr. 0 mieten oder Schloss schließen?", "Mieten", "Schloss schließen").Returns(Task.FromResult(true)); connector.Command.DoBook(bike).Returns(x => throw new Exception("Exception message.")); locks[0].CloseAsync().Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); bike.State.Value.Returns(InUseStateEnum.Disposable); var subsequent = handler.HandleRequestOption1().Result; // Verify behaviour Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked bikesViewModel.ActionText = "One moment please..."; pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action bikesViewModel.ActionText = "Reading charging level..."; locks[0].GetBatteryPercentageAsync(); bikesViewModel.ActionText = "Renting bike..."; connector.Command.DoBook(bike); bikesViewModel.ActionText = ""; viewService.DisplayAlert("Error when renting the bike!", "Attention: Lock is closed!\r\nException message.", "OK"); bikesViewModel.ActionText = "Verschließe Schloss..."; locks[0].CloseAsync(); bikesViewModel.ActionText = "Disconnecting lock..."; locks.DisconnectAsync(Arg.Any(), Arg.Any()); bikesViewModel.ActionText = "Updating..."; pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again bikesViewModel.ActionText = ""; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.AreEqual("Reserve bike", subsequent.ButtonText); Assert.IsTrue(subsequent.IsButtonVisible); Assert.AreEqual("DisposableDisconnected", subsequent.LockitButtonText); Assert.IsFalse(subsequent.IsLockitButtonVisible); } } }