using System; using System.Threading; using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock; using ShareeBike.Services.BluetoothLock; using ShareeBike.Services.Geolocation; using ShareeBike.Repository.Request; using ShareeBike.Model.Connector; using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand; using ShareeBike.Model; using ShareeBike.Repository.Exception; using ShareeBike.Model.State; namespace SharedBusinessLogic.Tests.Model.Bikes.BikeInfoNS.BikeNS.Command { [TestFixture] public class TestEndRentalCommand { /// /// Use case: End rental. /// Final state: Rental ended successfully /// [Test] [Ignore("bike.State.Value should be FeedbackPending")] public async Task TestReturnClosingLockLocationAvailable() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); var closingLockLocation = Substitute.For(); closingLockLocation.Latitude.Returns(7); closingLockLocation.Longitude.Returns(9); closingLockLocation.Timestamp.Returns(DateTimeOffset.MinValue); bike.LockInfo.Location.Returns((IGeolocation)closingLockLocation); // When locking bike geolocation was available. var endRentalLocation = closingLockLocation; var bookingFinished = connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; listener.ReportStep(Step.ReturnBike); var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); }); Assert.That(bookingFinished, Is.Not.Null); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.FeedbackPending)); } // // Use case: End rental. // Final state: Rental ended successfully // [Test] [Ignore("bike.State.Value should be FeedbackPending")] public async Task TestReturnLastGeolocationNullLockInReach() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.Location.Returns((IGeolocation)null); // Simulate that when locking bike geolocation was not available var newLockLocation = Substitute.For(); // Get geolocation from end rental newLockLocation.Latitude.Returns(7); newLockLocation.Longitude.Returns(9); newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue); var endRentalLocation = geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener); var bookingFinished = connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); var endRentalLocation = await geolocation.GetAsync(Arg.Any(), Arg.Any()); listener.ReportStep(Step.ReturnBike); var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); }); Assert.That(bookingFinished, Is.Not.Null); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.FeedbackPending)); } /// /// Use case: End rental. /// Final state: Still rented. /// [Test] public async Task TestReturnLastGeolocatonNullLockOutOfReachNoGPSDataDoNotReturn() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.Location.Returns((IGeolocation)null); // When locking bike geolocation was not available. locks[0].GetDeviceState().Returns(DeviceState.Disconnected); // Simulate bike not in reach. await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener); var task = connector.Command.DidNotReceive().DoReturn(Arg.Any(), Arg.Any()); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); await listener.ReportStateAsync(State.NoGPSData, "There is no geolocation information available since lock is not connected"); }); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked)); } /// /// Use case: End rental. /// Final state: Still rented. /// [Test] public void TestReturnLastGeolocatonNullLockInReachGetGeolocationException() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.Location.Returns((IGeolocation)null); // When locking bike geolocation was not available. locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns>(x => throw new Exception("Ups...")); // Simulate error query for geolocation. Assert.That( async () => await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener), Throws.InstanceOf()); connector.Command.DidNotReceive().DoReturn(Arg.Any(), Arg.Any()); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); var endRentalLocation = await geolocation.GetAsync(Arg.Any(), Arg.Any()); await listener.ReportStateAsync(State.GeneralQueryLocationFailed, "Ups..."); }); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked)); } /// /// Use case: End rental. /// Final state: Still rented. /// [Test] [Ignore("bookingFinished should be null")] public void TestReturnLastGeolocatonNullReturnException() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.Location.Returns((IGeolocation)null); // When locking bike geolocation was not available. locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. var newLockLocation = Substitute.For(); // Get geolocation from end rental newLockLocation.Latitude.Returns(7); newLockLocation.Longitude.Returns(9); newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue); var endRentalLocation = geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); var bookingFinished = connector.Command.DoReturn(Arg.Any(), Arg.Any()).Returns(x => throw new Exception("Ups...")); Assert.That( async () => await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); var endRentalLocation = await geolocation.GetAsync(Arg.Any(), Arg.Any()); listener.ReportStep(Step.ReturnBike); var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); await listener.ReportStateAsync(State.GeneralEndRentalError, "Ups..."); }); Assert.That(bookingFinished, Is.Null); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked)); } /// /// Use case: End rental. /// Final state: Still rented. /// [Test] [Ignore("bookingFinished should be null")] public void TestReturnReturnNoWebException() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.Location.Returns((IGeolocation)null); // When locking bike geolocation was not available. locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. var newLockLocation = Substitute.For(); // Get geolocation from end rental newLockLocation.Latitude.Returns(7); newLockLocation.Longitude.Returns(9); newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue); var endRentalLocation = geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); var bookingFinished = connector.Command.DoReturn(Arg.Any(), Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info.", new System.Exception("hoppla"))); Assert.That( async () => await InvokeAsync( bike, geolocation, locks, dateTime, () => false, // isConnectedDelegate (isConnected) => connector, listener), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); var endRentalLocation = await geolocation.GetAsync(Arg.Any(), Arg.Any()); listener.ReportStep(Step.ReturnBike); var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); await listener.ReportStateAsync(State.WebConnectFailed, "Context info."); }); Assert.That(bookingFinished, Is.Null); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked)); } /// /// Use case: End rental. /// Final state: Still rented. /// [Test] [Ignore("Exception needs to be initialized correctly. bookingFinished should be null")] public void TestReturnNotAtStationException() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.Id.Returns("1545"); bike.LockInfo.Location.Returns((IGeolocation)null); // When locking bike geolocation was not available. locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. var newLockLocation = Substitute.For(); // Get geolocation from end rental newLockLocation.Latitude.Returns(7); newLockLocation.Longitude.Returns(9); newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue); var endRentalLocation = geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(newLockLocation)); NotAtStationException.IsNotAtStation("Failure 2178: bike 1545 out of GEO fencing. 15986 meter distance to next station 42. OK: bike 1545 locked confirmed", out NotAtStationException notAtStationException); notAtStationException.Distance.Returns(15986); notAtStationException.StationNr.Returns("42"); var bookingFinished = connector.Command.DoReturn(Arg.Any(), Arg.Any()).Returns(x => throw notAtStationException); Assert.That( async () => await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); var endRentalLocation = await geolocation.GetAsync(Arg.Any(), Arg.Any()); listener.ReportStep(Step.ReturnBike); var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is(x => x.Latitude == 7 && x.Longitude == 9)); await listener.ReportStateAsync(State.NotAtStation, "End rental outside or at unsuitable station is not possible. Distance to next suitable station 42 is 15986 m.\r\n\r\nNote: Your GPS data may not be up to date. So if you are at a suitable station, please simply try again so that your location is queried again."); }); Assert.That(bookingFinished, Is.Null); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked)); } /// /// Use case: End rental. /// Final state: Still rented. /// [Test] [Ignore("Exception needs to be initialized correctly. bookingFinished should be null")] public void TestReturnNoGPSDataException() { var bike = Substitute.For(); var geolocation = Substitute.For(); var locks = Substitute.For(); static DateTime dateTime() => DateTime.MinValue; var connector = Substitute.For(); var listener = Substitute.For(); bike.State.Value.Returns(InUseStateEnum.Booked); bike.LockInfo.Location.Returns((IGeolocation)null); // When locking bike geolocation was not available. locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach. var endRentalLocation = geolocation.GetAsync(Arg.Any(), Arg.Any()) .Returns((IGeolocation)null); NoGPSDataException.IsNoGPSData("Failure 2245: No GPS data, state change forbidden.", out NoGPSDataException noGPSDataException); var bookingFinished = connector.Command.DoReturn(Arg.Any(), Arg.Any()).Returns(x => throw noGPSDataException); locks.DidNotReceive().DisconnectAsync(Arg.Any(), Arg.Any()); Assert.That( async () => await InvokeAsync( bike, geolocation, locks, dateTime, () => true, // isConnectedDelegate (isConnected) => connector, listener), Throws.InstanceOf()); // Verify behavior Received.InOrder(async () => { listener.ReportStep(Step.GetLocation); var location = bike.LockInfo.Location; locks[0].GetDeviceState(); var endRentalLocation = await geolocation.GetAsync(Arg.Any(), Arg.Any()); listener.ReportStep(Step.ReturnBike); var bookingFinished = await connector.Command.DoReturn(bike, null); await listener.ReportStateAsync(State.NoGPSData, "Failure 2245: No GPS data, state change forbidden."); }); Assert.That(bookingFinished, Is.Null); Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked)); } } }