using System; using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock; using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS; using ShareeBike.Model.Connector; using ShareeBike.Model.State; using ShareeBike.Services.BluetoothLock; using ShareeBike.View; using ShareeBike.ViewModel; using ShareeBike.ViewModel.Bikes; using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; using StartReservationCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartReservationCommand; using ConnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.ConnectAndGetStateCommand; using StartRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand; using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand; using ShareeBike.ViewModel.Bikes.Bike; namespace SharedBusinessLogic.Tests.ViewModel.Bikes.Bike { [TestFixture] public class TestStartReservationOrRentalActionViewModel { /// /// Use case: Reserve / Rent. /// Initial state: Disposable UnknownDisconnected, Lock currently in reach /// Final state: Booked Closed /// [Test] public async Task TestReserveLockInReachRent() { var bike = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var listener = Substitute.For(); var startReservationOrRentalActionViewModel = new StartReservationOrRentalActionViewModel( bike, () => pollingManager, viewService, bikesViewModel); bike.Id.Returns("0"); bike.AaRideType.Returns(AaRideType.NoAaRide); bike.ReserveBikeAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(StartReservationCommand.Step.ReserveBike); return Task.CompletedTask; }); bike.ConnectAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(ConnectCommand.Step.ConnectLock); startReservationOrRentalActionViewModel.ReportStep(ConnectCommand.Step.GetLockingState); bike.LockInfo.State.Returns(LockingState.Closed); return Task.CompletedTask; }); viewService.DisplayAlert( "Rent bike?", "Do you want to rent the bike and open the lock?", "Rent bike & open lock", "Cancel") .Returns(Task.FromResult(true)); bike.RentBikeAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(StartRentalCommand.Step.RentBike); return Task.CompletedTask; }); await startReservationOrRentalActionViewModel.StartReservationOrRentalAsync(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.State.Returns(LockingState.Closed); // 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.StartRentalProcess(Arg.Is( x => x.BikeId == "0" && x.State == CurrentRentalProcess.StartReservationOrRental && x.StepIndex == 1 && x.Result == CurrentStepStatus.None)); bikesViewModel.ActionText = "Reserving bike..."; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; bikesViewModel.RentalProcess.StepIndex = 2; bikesViewModel.RentalProcess.Result = CurrentStepStatus.None; bikesViewModel.ActionText = "Connecting lock..."; viewService.DisplayAlert( "Rent bike?", "Do you want to rent the bike and open the lock?", "Rent bike & open lock", "Cancel"); bikesViewModel.ActionText = "Renting bike..."; startReservationOrRentalActionViewModel.ContinueWithOpenLock = true; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.RentalProcess.State = CurrentRentalProcess.None; bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); } /// /// Use case: Reserve / Rent. /// Initial state: Disposable UnknownDisconnected, Lock out of reach /// Final state: Reserved UnknownDisconnected /// [Test] public async Task TestReserveLockOutOfReach() { var bike = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var listener = Substitute.For(); var startReservationOrRentalActionViewModel = new StartReservationOrRentalActionViewModel( bike, () => pollingManager, viewService, bikesViewModel); bike.Id.Returns("0"); bike.AaRideType.Returns(AaRideType.NoAaRide); bike.ReserveBikeAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(StartReservationCommand.Step.ReserveBike); return Task.CompletedTask; }); bike.ConnectAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(ConnectCommand.Step.ConnectLock); startReservationOrRentalActionViewModel.ReportStep(ConnectCommand.Step.GetLockingState); bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); return Task.CompletedTask; }); await startReservationOrRentalActionViewModel.StartReservationOrRentalAsync(); bike.State.Value.Returns(InUseStateEnum.Reserved); bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // 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.StartRentalProcess(Arg.Is( x => x.BikeId == "0" && x.State == CurrentRentalProcess.StartReservationOrRental && x.StepIndex == 1 && x.Result == CurrentStepStatus.None)); bikesViewModel.ActionText = "Reserving bike..."; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; bikesViewModel.RentalProcess.StepIndex = 2; bikesViewModel.RentalProcess.Result = CurrentStepStatus.None; bikesViewModel.ActionText = "Connecting lock..."; startReservationOrRentalActionViewModel.ContinueWithOpenLock = false; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; viewService.DisplayAlert( "Bike is reserved", "If you do not rent the bike within the next few minutes, your reservation will expire.", "OK"); bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.RentalProcess.State = CurrentRentalProcess.None; bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); } /// /// Use case: Reserve / Rent. /// Initial state: Disposable UnknownDisconnected, Lock currently in reach /// Final state: Reserved UnknownDisconnected /// [Test] public async Task TestReserveLockInReachCancel() { var bike = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var listener = Substitute.For(); var startReservationOrRentalActionViewModel = new StartReservationOrRentalActionViewModel( bike, () => pollingManager, viewService, bikesViewModel); bike.Id.Returns("0"); bike.AaRideType.Returns(AaRideType.NoAaRide); bike.ReserveBikeAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(StartReservationCommand.Step.ReserveBike); return Task.CompletedTask; }); bike.ConnectAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(ConnectCommand.Step.ConnectLock); startReservationOrRentalActionViewModel.ReportStep(ConnectCommand.Step.GetLockingState); bike.LockInfo.State.Returns(LockingState.Closed); return Task.CompletedTask; }); viewService.DisplayAlert( "Rent bike?", "Do you want to rent the bike and open the lock?", "Rent bike & open lock", "Cancel") .Returns(Task.FromResult(false)); await startReservationOrRentalActionViewModel.StartReservationOrRentalAsync(); bike.State.Value.Returns(InUseStateEnum.Reserved); bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // 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.StartRentalProcess(Arg.Is( x => x.BikeId == "0" && x.State == CurrentRentalProcess.StartReservationOrRental && x.StepIndex == 1 && x.Result == CurrentStepStatus.None)); bikesViewModel.ActionText = "Reserving bike..."; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; bikesViewModel.RentalProcess.StepIndex = 2; bikesViewModel.RentalProcess.Result = CurrentStepStatus.None; bikesViewModel.ActionText = "Connecting lock..."; viewService.DisplayAlert( "Rent bike?", "Do you want to rent the bike and open the lock?", "Rent bike & open lock", "Cancel"); startReservationOrRentalActionViewModel.ContinueWithOpenLock = false; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Succeeded; viewService.DisplayAlert( "Bike is reserved", "If you do not rent the bike within the next few minutes, your reservation will expire.", "OK"); bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.RentalProcess.State = CurrentRentalProcess.None; bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); } /// /// Use case: Reserve / Rent. /// Initial state: Disposable UnknownDisconnected /// Final state: Disposable UnknownDisconnected /// [Test] public async Task TestReserveException() { var bike = Substitute.For(); var pollingManager = Substitute.For(); var viewService = Substitute.For(); var bikesViewModel = Substitute.For(); var listener = Substitute.For(); var startReservationOrRentalActionViewModel = new StartReservationOrRentalActionViewModel( bike, () => pollingManager, viewService, bikesViewModel); bike.Id.Returns("0"); bike.AaRideType.Returns(AaRideType.NoAaRide); bike.ReserveBikeAsync(Arg.Any()).Returns(x => { startReservationOrRentalActionViewModel.ReportStep(StartReservationCommand.Step.ReserveBike); startReservationOrRentalActionViewModel.ReportStateAsync(StartReservationCommand.State.GeneralStartReservationError, "details").Wait(); return Task.CompletedTask; }); await startReservationOrRentalActionViewModel.StartReservationOrRentalAsync(); bike.State.Value.Returns(InUseStateEnum.Reserved); bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // 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.StartRentalProcess(Arg.Is( x => x.BikeId == "0" && x.State == CurrentRentalProcess.StartReservationOrRental && x.StepIndex == 1 && x.Result == CurrentStepStatus.None)); bikesViewModel.ActionText = "Reserving bike..."; bikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed; bikesViewModel.ActionText = String.Empty; viewService.DisplayAdvancedAlert( "Bike could not be reserved!", "details", "Please try again.", "OK"); startReservationOrRentalActionViewModel.ContinueWithOpenLock = false; bikesViewModel.ActionText = "One moment please..."; pollingManager.StartAsync(); // polling must be restarted again bikesViewModel.RentalProcess.State = CurrentRentalProcess.None; bikesViewModel.ActionText = string.Empty; bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked }); } } }