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

492 lines
20 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using NSubstitute;
using NUnit.Framework;
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
using ShareeBike.Model.Connector;
using ShareeBike.Model.State;
using ShareeBike.Repository.Request;
using ShareeBike.Services.BluetoothLock;
using ShareeBike.Services.BluetoothLock.Tdo;
using ShareeBike.Services.Geolocation;
using Geolocation = ShareeBike.Services.Geolocation.Geolocation;
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
using NSubstitute.ExceptionExtensions;
using ShareeBike.Services.BluetoothLock.Exception;
using ShareeBike.Repository.Exception;
using Newtonsoft.Json;
using ShareeBike.Repository.Response;
using ShareeBike.ViewModel;
namespace SharedBusinessLogic.Tests.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
[TestFixture]
public class TestCloseCommand
{
/// <summary>
/// Use case: Close lock
/// </summary>
[Test]
public async Task TestClose()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
await InvokeAsync<TestCloseCommand>(
bike,
geolocation,
locks,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener, null);
// Verify that location is kept because bike might be returned later.
Assert.That(
bike.LockInfo.Location.Latitude,
Is.EqualTo(47.99).Within(0.001));
Assert.That(
bike.LockInfo.Location.Longitude,
Is.EqualTo(7.78).Within(0.001));
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.Closed));
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
listener.Received().ReportStep(Step.WaitStopPollingQueryLocation);
listener.Received().ReportStep(Step.QueryLocationTerminated);
await connector.Command.UpdateLockingStateAsync(bike, Arg.Is<LocationDto>(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
}
[Test]
public async Task TestCloseGeolocationServiceGetAsyncFails()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
var startOfTest = DateTime.Now;
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Throws(new Exception("Ups no location..."));
locks[0].CloseAsync()
.Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync<TestCloseCommand>(
bike,
geolocation,
locks,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener, null);
// Verify that location is kept because bike might be returned later.
Assert.That(
bike.LockInfo.Location,
Is.Null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
await listener.Received().ReportStateAsync(State.StartGeolocationException, "Ups no location...");
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
});
}
[Test]
public void TestCloseCloseFailsOutOfReachException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns<Task<LockitLockingState?>>(x => throw new OutOfReachException());
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync<TestCloseCommand>(
bike,
geolocation,
locks,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener, null),
Throws.InstanceOf<OutOfReachException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await listener.Received().ReportStateAsync(State.OutOfReachError, "Exception of type 'ShareeBike.Services.BluetoothLock.Exception.OutOfReachException' was thrown.");
});
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.UnknownDisconnected));
}
[Test]
public void TestCloseCloseFailsCouldntCloseMovingException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns<Task<LockitLockingState?>>(x => throw new CouldntCloseMovingException());
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync<TestCloseCommand>(
bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf<CouldntCloseMovingException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await listener.Received().ReportStateAsync(State.CouldntCloseMovingError, "The process is motion sensitive. Step close to the lock, do not move, and try again.");
await connector.Command.UpdateLockingStateAsync(bike, Arg.Is<LocationDto>(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.Open));
}
[Test]
public void TestCloseCloseFailsCouldntCloseBoltBlockedException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns<Task<LockitLockingState?>>(x => throw new CouldntCloseBoltBlockedException());
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf<CouldntCloseBoltBlockedException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await listener.Received().ReportStateAsync(State.CouldntCloseBoltBlockedError, "Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.");
await connector.Command.UpdateLockingStateAsync(bike, Arg.Is<LocationDto>(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.UnknownFromHardwareError));
}
[Test]
public void TestCloseCloseFailsCouldntCloseBoltBlockedExceptionOpen()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns<Task<LockitLockingState?>>(x => throw new CouldntCloseBoltBlockedException(LockingState.Open));
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf<CouldntCloseBoltBlockedException>());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await listener.Received().ReportStateAsync(State.CouldntCloseBoltBlockedError, "Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.");
await connector.Command.UpdateLockingStateAsync(bike, Arg.Is<LocationDto>(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.Open));
}
[Test]
public void TestCloseCloseFailsCouldntCloseInconsistentState()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns<Task<LockitLockingState?>>(x => throw new CouldntCloseInconsistentStateExecption(LockitLockingState.Unknown.GetLockingState()));
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf<CouldntCloseInconsistentStateExecption>());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await listener.Received().ReportStateAsync(State.GeneralCloseError, "Lock reports unknown bold position. Lock could be closed or open. Please try again or contact customer support.");
await connector.Command.UpdateLockingStateAsync(bike, Arg.Is<LocationDto>(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.UnknownFromHardwareError));
}
[Test]
public void TestCloseCloseFailsException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var command = Substitute.For<ICommand>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns<Task<LockitLockingState?>>(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Booked);
Assert.That(
async () => await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf<Exception>());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await listener.ReportStateAsync(State.GeneralCloseError, "Exception message.");
await connector.Command.UpdateLockingStateAsync(bike, Arg.Is<LocationDto>(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
Assert.That(
bike.LockInfo.State,
Is.EqualTo(LockingState.UnknownDisconnected));
}
[Test]
public async Task TestCloseWaitGeolocationServiceGetAsyncFails()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var command = Substitute.For<ICommand>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>()).Returns(Task.FromException<IGeolocation>(new Exception("Ups, some error...")));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
listener.Received().ReportStep(Step.WaitStopPollingQueryLocation);
await listener.Received().ReportStateAsync(State.WaitGeolocationException, "Ups, some error...");
listener.Received().ReportStep(Step.QueryLocationTerminated);
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
});
}
[Test]
public async Task TestCloseUpdateLockingStateFailsWebConnectFailureException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var command = Substitute.For<ICommand>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x => throw new WebConnectFailureException("Context info.", new System.Exception("hoppla")));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
await listener.ReportStateAsync(State.WebConnectFailed, "Context info.");
});
}
[Test]
public async Task TestCloseUpdateLockingStateFailsResponseException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var command = Substitute.For<ICommand>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x =>
throw new ReturnBikeException(JsonConvert.DeserializeObject<DoReturnResponse>(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message."));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
await listener.ReportStateAsync(State.ResponseIsInvalid, "Outer message.");
});
}
[Test]
public async Task TestCloseUpdateLockingStateFailsException()
{
var bike = Substitute.For<IBikeInfoMutable>();
var connector = Substitute.For<IConnector>();
var command = Substitute.For<ICommand>();
var geolocation = Substitute.For<IGeolocationService>();
var locks = Substitute.For<ILocksService>();
var listener = Substitute.For<ICloseCommandListener>();
var viewUpdateManager = Substitute.For<IPollingUpdateTaskManager>();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync<TestCloseCommand>(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any<CancellationToken?>(), Arg.Any<DateTime?>());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
await listener.ReportStateAsync(State.BackendUpdateFailed, "Exception message.");
});
}
}
}