sharee.bike-App/SharedBusinessLogic.Tests/Model/Bikes/BikeInfoNS/BikeNS/Command/TestEndRentalCommand.cs
2024-04-09 12:53:23 +02:00

470 lines
17 KiB
C#

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
{
/// <summary>
/// Use case: End rental.
/// Final state: Rental ended successfully
/// </summary>
[Test]
[Ignore("bike.State.Value should be FeedbackPending")]
public async Task TestReturnClosingLockLocationAvailable()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
bike.State.Value.Returns(InUseStateEnum.Booked);
var closingLockLocation = Substitute.For<IGeolocation>();
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<LocationDto>(x => x.Latitude == 7 && x.Longitude == 9));
await InvokeAsync<TestEndRentalCommand>(
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<LocationDto>(x => x.Latitude == 7 && x.Longitude == 9));
});
Assert.That(bookingFinished, Is.Not.Null);
Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.FeedbackPending));
}
// <summary>
// Use case: End rental.
// Final state: Rental ended successfully
// </summary>
[Test]
[Ignore("bike.State.Value should be FeedbackPending")]
public async Task TestReturnLastGeolocationNullLockInReach()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<IGeolocation>(); // Get geolocation from end rental
newLockLocation.Latitude.Returns(7);
newLockLocation.Longitude.Returns(9);
newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue);
var endRentalLocation = geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns(Task.FromResult(newLockLocation));
locks[0].GetDeviceState().Returns(DeviceState.Connected); // Simulate bike in reach.
await InvokeAsync<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener);
var bookingFinished = connector.Command.DoReturn(bike, Arg.Is<LocationDto>(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<CancellationToken?>(), Arg.Any<DateTime?>());
listener.ReportStep(Step.ReturnBike);
var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is<LocationDto>(x => x.Latitude == 7 && x.Longitude == 9));
});
Assert.That(bookingFinished, Is.Not.Null);
Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.FeedbackPending));
}
/// <summary>
/// Use case: End rental.
/// Final state: Still rented.
/// </summary>
[Test]
public async Task TestReturnLastGeolocatonNullLockOutOfReachNoGPSDataDoNotReturn()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener);
var task = connector.Command.DidNotReceive().DoReturn(Arg.Any<IBikeInfoMutable>(), Arg.Any<LocationDto>());
// 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));
}
/// <summary>
/// Use case: End rental.
/// Final state: Still rented.
/// </summary>
[Test]
public void TestReturnLastGeolocatonNullLockInReachGetGeolocationException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns<Task<IGeolocation>>(x => throw new Exception("Ups...")); // Simulate error query for geolocation.
Assert.That(
async () => await InvokeAsync<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener),
Throws.InstanceOf<Exception>());
connector.Command.DidNotReceive().DoReturn(Arg.Any<IBikeInfoMutable>(), Arg.Any<LocationDto>());
// Verify behavior
Received.InOrder(async () =>
{
listener.ReportStep(Step.GetLocation);
var location = bike.LockInfo.Location;
locks[0].GetDeviceState();
var endRentalLocation = await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
await listener.ReportStateAsync(State.GeneralQueryLocationFailed, "Ups...");
});
Assert.That(bike.State.Value, Is.EqualTo(InUseStateEnum.Booked));
}
/// <summary>
/// Use case: End rental.
/// Final state: Still rented.
/// </summary>
[Test]
[Ignore("bookingFinished should be null")]
public void TestReturnLastGeolocatonNullReturnException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<IGeolocation>(); // Get geolocation from end rental
newLockLocation.Latitude.Returns(7);
newLockLocation.Longitude.Returns(9);
newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue);
var endRentalLocation = geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns(Task.FromResult(newLockLocation));
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns(Task.FromResult(newLockLocation));
var bookingFinished = connector.Command.DoReturn(Arg.Any<IBikeInfoMutable>(), Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x =>
throw new Exception("Ups..."));
Assert.That(
async () => await InvokeAsync<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener),
Throws.InstanceOf<Exception>());
// Verify behavior
Received.InOrder(async () =>
{
listener.ReportStep(Step.GetLocation);
var location = bike.LockInfo.Location;
locks[0].GetDeviceState();
var endRentalLocation = await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.ReportStep(Step.ReturnBike);
var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is<LocationDto>(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));
}
/// <summary>
/// Use case: End rental.
/// Final state: Still rented.
/// </summary>
[Test]
[Ignore("bookingFinished should be null")]
public void TestReturnReturnNoWebException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<IGeolocation>(); // Get geolocation from end rental
newLockLocation.Latitude.Returns(7);
newLockLocation.Longitude.Returns(9);
newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue);
var endRentalLocation = geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns(Task.FromResult(newLockLocation));
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns(Task.FromResult(newLockLocation));
var bookingFinished = connector.Command.DoReturn(Arg.Any<IBikeInfoMutable>(), Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x
=> throw new WebConnectFailureException("Context info.", new System.Exception("hoppla")));
Assert.That(
async () => await InvokeAsync<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => false, // isConnectedDelegate
(isConnected) => connector,
listener),
Throws.InstanceOf<WebConnectFailureException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.ReportStep(Step.GetLocation);
var location = bike.LockInfo.Location;
locks[0].GetDeviceState();
var endRentalLocation = await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.ReportStep(Step.ReturnBike);
var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is<LocationDto>(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));
}
/// <summary>
/// Use case: End rental.
/// Final state: Still rented.
/// </summary>
[Test]
[Ignore("Exception needs to be initialized correctly. bookingFinished should be null")]
public void TestReturnNotAtStationException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<IGeolocation>(); // Get geolocation from end rental
newLockLocation.Latitude.Returns(7);
newLockLocation.Longitude.Returns(9);
newLockLocation.Timestamp.Returns(DateTimeOffset.MinValue);
var endRentalLocation = geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns(Task.FromResult(newLockLocation));
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>())
.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<IBikeInfoMutable>(), Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x
=> throw notAtStationException);
Assert.That(
async () => await InvokeAsync<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener),
Throws.InstanceOf<NotAtStationException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.ReportStep(Step.GetLocation);
var location = bike.LockInfo.Location;
locks[0].GetDeviceState();
var endRentalLocation = await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.ReportStep(Step.ReturnBike);
var bookingFinished = await connector.Command.DoReturn(bike, Arg.Is<LocationDto>(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));
}
/// <summary>
/// Use case: End rental.
/// Final state: Still rented.
/// </summary>
[Test]
[Ignore("Exception needs to be initialized correctly. bookingFinished should be null")]
public void TestReturnNoGPSDataException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
static DateTime dateTime() => DateTime.MinValue;
var connector = Substitute.For<IConnector>();
var listener = Substitute.For<IEndRentalCommandListener>();
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<CancellationToken?>(), Arg.Any<DateTime?>())
.Returns((IGeolocation)null);
NoGPSDataException.IsNoGPSData("Failure 2245: No GPS data, state change forbidden.", out NoGPSDataException noGPSDataException);
var bookingFinished = connector.Command.DoReturn(Arg.Any<IBikeInfoMutable>(), Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x =>
throw noGPSDataException);
locks.DidNotReceive().DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
Assert.That(
async () => await InvokeAsync<TestEndRentalCommand>(
bike,
geolocation,
locks,
dateTime,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener),
Throws.InstanceOf<NoGPSDataException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.ReportStep(Step.GetLocation);
var location = bike.LockInfo.Location;
locks[0].GetDeviceState();
var endRentalLocation = await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
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));
}
}
}