using System; using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; using ShareeBike.Model.Bikes.BikeInfoNS.CopriLock; using ShareeBike.Model.Connector; using ShareeBike.Model.Device; using ShareeBike.Model.User; using ShareeBike.Repository.Exception; using ShareeBike.View; using ShareeBike.ViewModel; using ShareeBike.ViewModel.Bikes; using ShareeBike.ViewModel.Bikes.Bike.CopriLock.RequestHandler; namespace SharedBusinessLogic.Tests.ViewModel.Bikes.Bike.CopriLock.RequestHandler { public class TestDisposableClosed { /// /// Use case: Start booking and cancel. /// Final state: Same as initial state. /// [Test] public void TestBookAndReleaseCancel() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, "Rent bike Nr. 0 and open lock?", "Yes", "No").Returns(Task.FromResult(false)); // User chooes "No" var subsequent = handler.HandleRequestOption1().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked viewService.DisplayAlert( string.Empty, "Rent bike Nr. 0 and open lock?", "Yes", "No"); bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Try book bike and release from station. /// Final state: Bike is disposable. /// [Test] public void TestBookAndReleaseBookAndOpenAyncFailsWebConnectFailureException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, string.Format("Rent bike {0} and open lock?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Closed); // Locking state does not change. connector.Command.BookAndOpenAync(bike).Returns(x => throw new WebConnectFailureException("Context info", new Exception("Tst"))); bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Disposable); // State remains available because booking request failed. 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 = "Renting bike..."; connector.Command.BookAndOpenAync(bike); // Booking must be performed bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert( "Connection to booking system not possible.", "A stable Internet connection is required. Connect to WIFI or to mobile network and activate mobile data. 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.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Try book bike and release from station. /// Final state: Bike is disposable. /// [Test] public void TestBookAndReleaseBookAndOpenAyncFailsException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, string.Format("Rent bike {0} and open lock?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Closed); // Locking state does not change. connector.Command.BookAndOpenAync(bike).Returns(x => throw new Exception("Some error", new Exception("Inner exception"))); bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Disposable); // State remains available because booking request failed. 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 = "Renting bike..."; connector.Command.BookAndOpenAync(bike); // Booking must be performed bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert( "Bike could not be rented!", "Some error", "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.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Book bike and release from station. /// Final state: Bike is booked and released from station. /// [Test] public void TestBookAndRelease() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, string.Format("Rent bike {0} and open lock?", "Nr. 0"), "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Open); // After Booking and opening, lock state is open. bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Booked); // After Booking and opening, state is 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 = "Renting bike..."; connector.Command.BookAndOpenAync(bike); // Booking must be performed 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 Assert.That( subsequent.ButtonText, Is.EqualTo("BookedOpen")); Assert.That( subsequent.IsButtonVisible, Is.False); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Open lock")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Cancel reserving. /// Final state: Same as initial state. /// [Test] public void TestReserveCancel() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); bike.TariffDescription.MaxReservationTimeSpan.Returns(TimeSpan.FromMinutes(15)); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(false)); // User chooes "No" var subsequent = handler.HandleRequestOption2().Result; // Verify behavior Received.InOrder(() => { bikesViewModel.Received(1).IsIdle = false; // GUI must be locked viewService.DisplayAlert( string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No"); bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); // Verify state after action Assert.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Try book reserve bike. /// Final state: Bike is disposable. /// [Test] public void TestReserveDoReserveFailsBookingDeclinedException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); bike.TariffDescription.MaxReservationTimeSpan.Returns(TimeSpan.FromMinutes(15)); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Closed); // Locking state does not change. connector.Command.DoReserve(bike).Returns(x => throw new BookingDeclinedException(4)); bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Disposable); // State remains available because booking request failed. var subsequent = handler.HandleRequestOption2().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 = "Reserving bike..."; connector.Command.DoReserve(bike); // Booking must be performed bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert( "Hint", "A reservation of bike Nr. 0 was rejected because the maximum allowed number of 4 reservations/rentals had already been made.", "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.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Try book reserve bike. /// Final state: Bike is disposable. /// [Test] public void TestReserveDoReserveFailsWebConnectFailureException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); bike.TariffDescription.MaxReservationTimeSpan.Returns(TimeSpan.FromMinutes(15)); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Closed); // Locking state does not change. connector.Command.DoReserve(bike).Returns(x => throw new WebConnectFailureException("Context info", new Exception("Tst"))); bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Disposable); // State remains available because booking request failed. var subsequent = handler.HandleRequestOption2().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 = "Reserving bike..."; connector.Command.DoReserve(bike); // Booking must be performed bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert( "Connection to booking system not possible.", "A stable Internet connection is required. Connect to WIFI or to mobile network and activate mobile data. 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.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Try book reserve bike. /// Final state: Bike is disposable. /// [Test] public void TestReserveDoReserveFailsException() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); bike.TariffDescription.MaxReservationTimeSpan.Returns(TimeSpan.FromMinutes(15)); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Closed); // Locking state does not change. connector.Command.DoReserve(bike).Returns(x => throw new Exception("Some error", new Exception("Inner exception"))); bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Disposable); // State remains available because booking request failed. var subsequent = handler.HandleRequestOption2().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 = "Reserving bike..."; connector.Command.DoReserve(bike); // Booking must be performed bikesViewModel.ActionText = string.Empty; viewService.DisplayAlert( "Bike could not be reserved!", "Some error", "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.That( subsequent.ButtonText, Is.EqualTo("Start rental & Open lock")); Assert.That( subsequent.IsButtonVisible, Is.True); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Reserve / Rent")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } /// /// Use case: Reserve bike. /// Final state: Bike is booked and released from station. /// [Test] public void TestReserve() { var bike = Substitute.For(); var connector = Substitute.For(); var command = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var activeUser = Substitute.For(); bike.TariffDescription.MaxReservationTimeSpan.Returns(TimeSpan.FromMinutes(15)); var handler = new DisposableClosed( bike, () => true, // isConnectedDelegate (isConnected) => connector, () => pollingManager, Substitute.For(), viewService, bikesViewModel, activeUser); bike.Id.Returns("0"); viewService.DisplayAlert( string.Empty, "Reserve bike Nr. 0 free of charge for 15 min?", "Yes", "No").Returns(Task.FromResult(true)); bike.LockInfo.State.Returns(LockingState.Open); // After Booking and opening, lock state is open. bike.State.Value.Returns(ShareeBike.Model.State.InUseStateEnum.Booked); // After Booking and opening, lock state is open. var subsequent = handler.HandleRequestOption2().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 = "Reserving bike..."; connector.Command.DoReserve(bike); // Booking must be performed 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 Assert.That( subsequent.ButtonText, Is.EqualTo("BookedOpen")); Assert.That( subsequent.IsButtonVisible, Is.False); Assert.That( subsequent.LockitButtonText, Is.EqualTo("Open lock")); Assert.That( subsequent.IsLockitButtonVisible, Is.True); } } }