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
{
///
/// Use case: Close lock
///
[Test]
public async Task TestClose()
{
var bike = Substitute.For();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).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(
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(), Arg.Any());
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(x => x.Latitude == 47.99 && x.Longitude == 7.78));
});
}
[Test]
public async Task TestCloseGeolocationServiceGetAsyncFails()
{
var bike = Substitute.For();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
var startOfTest = DateTime.Now;
geolocation.GetAsync(Arg.Any(), Arg.Any()).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(
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(), Arg.Any());
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());
});
}
[Test]
public void TestCloseCloseFailsOutOfReachException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns>(x => throw new OutOfReachException());
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync(
bike,
geolocation,
locks,
() => true, // isConnectedDelegate
(isConnected) => connector,
listener, null),
Throws.InstanceOf());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns>(x => throw new CouldntCloseMovingException());
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync(
bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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(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();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns>(x => throw new CouldntCloseBoltBlockedException());
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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(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();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns>(x => throw new CouldntCloseBoltBlockedException(LockingState.Open));
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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(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();
var connector = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns>(x => throw new CouldntCloseInconsistentStateExecption(LockitLockingState.Unknown.GetLockingState()));
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Open);
Assert.That(
async () => await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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(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();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(new Geolocation.Builder { Latitude = 47.99, Longitude = 7.78 }.Build());
locks[0].CloseAsync()
.Returns>(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Booked);
Assert.That(
async () => await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null),
Throws.InstanceOf());
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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(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();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
geolocation.GetAsync(Arg.Any(), Arg.Any()).Returns(Task.FromException(new Exception("Ups, some error...")));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
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());
});
}
[Test]
public async Task TestCloseUpdateLockingStateFailsWebConnectFailureException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new WebConnectFailureException("Context info.", new System.Exception("hoppla")));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any());
await listener.ReportStateAsync(State.WebConnectFailed, "Context info.");
});
}
[Test]
public async Task TestCloseUpdateLockingStateFailsResponseException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x =>
throw new ReturnBikeException(JsonConvert.DeserializeObject(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message."));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any());
await listener.ReportStateAsync(State.ResponseIsInvalid, "Outer message.");
});
}
[Test]
public async Task TestCloseUpdateLockingStateFailsException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var listener = Substitute.For();
var viewUpdateManager = Substitute.For();
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new Exception("Exception message."));
bike.State.Value.Returns(InUseStateEnum.Booked);
await InvokeAsync(bike, geolocation, locks, () => true, (isConnected) => connector, listener, null);
// Verify behavior
Received.InOrder(async () =>
{
listener.Received().ReportStep(Step.StartingQueryingLocation);
await geolocation.GetAsync(Arg.Any(), Arg.Any());
listener.Received().ReportStep(Step.ClosingLock);
await locks.Received()[0].CloseAsync(); // Lock must be closed
await connector.Command.UpdateLockingStateAsync(bike, Arg.Any());
await listener.ReportStateAsync(State.BackendUpdateFailed, "Exception message.");
});
}
}
}