using System;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NSubstitute;
using NUnit.Framework;
using TINK.Model;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock;
using TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command;
using TINK.Model.Connector;
using TINK.Model.Device;
using TINK.Model.State;
using TINK.Model.User;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Services.Geolocation;
using TINK.View;
using TINK.ViewModel;
using TINK.ViewModel.Bikes;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
using static TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
namespace TestTINKLib.Fixtures.ObjectTests.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
[TestFixture]
public class TestBookedOpen
{
///
/// Test construction of object.
///
[Test]
public void Testctor()
{
var handler = new BookedOpen(
Substitute.For(),
() => true, // isConnectedDelegate
(isConnexted) => Substitute.For(),
Substitute.For(),
Substitute.For(),
() => Substitute.For(),
Substitute.For(),
Substitute.For(),
Substitute.For(),
Substitute.For());
// Verify state after action
Assert.AreEqual("Close lock", handler.ButtonText);
Assert.IsTrue(handler.IsButtonVisible);
Assert.AreEqual(string.Empty, handler.LockitButtonText);
Assert.That(handler.IsLockitButtonVisible, Is.False);
}
///
/// Use case: Close lock
/// Final state: Booked closed.
///
[Test]
public void TestClose()
{
var bike = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => Substitute.For(),
Substitute.For(),
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
Substitute.For());
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any()).Returns(x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
handler.ReportStep(Step.UpdateLockingState);
return Task.CompletedTask;
}
);
#else
locks[0].CloseAsync()
.Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.Closed); // Return lock state indicating success
var subsequent = handler.HandleRequestOption1().Result;
// Verify behavior
Received.InOrder(() =>
{
bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
bikesViewModel.ActionText = "One moment please...";
bikesViewModel.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
bikesViewModel.ActionText = "One moment please...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked Closed" after action
Assert.AreEqual("End rental", subsequent.ButtonText);
Assert.IsTrue(subsequent.IsButtonVisible);
Assert.AreEqual("Open lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
///
/// Use case: Close lock
///
[Test]
public void TestCloseCloseFailsOutOfReachException()
{
var bike = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => Substitute.For(),
Substitute.For(),
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
Substitute.For());
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any())
.Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
await handler.ReportStateAsync(CloseCommand.State.OutOfReachError, "");
throw new OutOfReachException();
}
);
#else
locks[0].CloseAsync()
.Returns>(x => throw new OutOfReachException());
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); /// If is fired lock state is set to state unknown because disconnected.
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
bikesViewModel.ActionText = string.Empty;
viewService.DisplayAlert("Lock could not be closed!", "Make sure you have granted Bluetooth permission to the app. Step as close as possible to the bike lock and try again.", "OK");
bikesViewModel.ActionText = "Updating...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked disconnected" after action
Assert.AreEqual("BookedDisconnected", subsequent.ButtonText);
Assert.IsFalse(subsequent.IsButtonVisible);
Assert.AreEqual("Search lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
[Test]
public void TestCloseCloseFailsCouldntCloseMovingException()
{
var bike = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => Substitute.For(),
Substitute.For(),
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
Substitute.For());
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any())
.Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
await handler.ReportStateAsync(CloseCommand.State.CouldntCloseMovingError, "");
throw new CouldntCloseMovingException();
}
);
bike.LockInfo.State.Returns(LockingState.Open); // Locking state is open when CouldntCloseMovingException occurs.
#else
locks[0].CloseAsync()
.Returns>(x => throw new CouldntCloseMovingException());
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
bikesViewModel.ActionText = string.Empty;
viewService.DisplayAlert("Lock could not be closed!", "The process is motion sensitive. Step close to the lock, do not move, and try again.", "OK");
bikesViewModel.ActionText = "Updating...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state after action
Assert.AreEqual("Close lock", subsequent.ButtonText);
Assert.IsTrue(subsequent.IsButtonVisible);
Assert.AreEqual(string.Empty, subsequent.LockitButtonText);
Assert.That(subsequent.IsLockitButtonVisible, Is.False);
}
[Test]
public void TestCloseCloseFailsCouldntCloseBoltBlockedException()
{
var bike = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => Substitute.For(),
Substitute.For(),
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
Substitute.For());
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any())
.Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
await handler.ReportStateAsync(CloseCommand.State.CouldntCloseBoltBlockedError, "");
throw new CouldntCloseBoltBlockedException();
}
);
bike.LockInfo.State.Returns(LockingState.UnknownFromHardwareError); // Locking state is unknown when CouldntCloseBoltBlockedException occurs.
#else
locks[0].CloseAsync()
.Returns>(x => throw new CouldntCloseBoltBlockedException());
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
bikesViewModel.ActionText = string.Empty;
viewService.DisplayAlert("Lock could not be closed!", "Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.", "OK");
bikesViewModel.ActionText = "Updating...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked disconnected" after action
Assert.AreEqual("Open lock", subsequent.ButtonText);
Assert.IsTrue(subsequent.IsButtonVisible);
Assert.AreEqual("Close lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
///
/// Use case: Close lock
/// Final state: Same as initial state.
///
[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 pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var activeUser = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => connector,
geolocation,
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
activeUser);
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any()).Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
await handler.ReportStateAsync(CloseCommand.State.GeneralCloseError, "Exception message.");
throw new Exception("Exception message.");
}
);
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // If CloseLock fires an exception of type Exception lock state is set to unknown.
#else
locks[0].CloseAsync()
.Returns>(x => throw new Exception("Exception message."));
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
#if USELOCALINSTANCE
locks.Received()[0].CloseAsync(); // Lock must be closed
#endif
bikesViewModel.ActionText = string.Empty;
viewService.DisplayAlert(
"Lock could not be closed!",
"Exception message.",
"OK"
);
bikesViewModel.ActionText = "Updating...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked Disconnected" after action
Assert.AreEqual("BookedDisconnected", subsequent.ButtonText);
Assert.IsFalse(subsequent.IsButtonVisible);
Assert.AreEqual("Search lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
///
/// Use case: Close lock
/// Final state: Booked closed.
///
[Test]
public void TestCloseUpdateLockingStateFailsWebConnectFailureException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var activeUser = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => connector,
geolocation,
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
activeUser);
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any()).Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
handler.ReportStep(Step.UpdateLockingState);
await handler.ReportStateAsync(CloseCommand.State.WebConnectFailed, "Context info");
}
);
bike.LockInfo.State.Returns(LockingState.Closed);
#else
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")));
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
#if USELOCALINSTANCE
locks.Received()[0].CloseAsync(); // Lock must be closed
connector.Command.UpdateLockingStateAsync(bike, Arg.Any());
#endif
bikesViewModel.ActionText = "Internet must be available for updating lock status. Please establish an Internet connection!";
bikesViewModel.ActionText = "One moment please...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked Closed" after action
Assert.AreEqual("End rental", subsequent.ButtonText);
Assert.IsTrue(subsequent.IsButtonVisible);
Assert.AreEqual("Open lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
///
/// Use case: close lock
/// Final state: Booked closed.
///
[Test]
public void TestCloseUpdateLockingStateFailsException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var activeUser = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => connector,
geolocation,
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
activeUser);
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any())
.Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
handler.ReportStep(Step.UpdateLockingState);
await handler.ReportStateAsync(CloseCommand.State.BackendUpdateFailed, "Exception message.");
}
);
bike.LockInfo.State.Returns(LockingState.Closed);
#else
locks[0].CloseAsync()
.Returns(LockitLockingState.Closed); // Return lock state indicating success
connector.Command.UpdateLockingStateAsync(bike, Arg.Any()).Returns(x => throw new Exception("Exception message."));
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
#if USELOCALINSTANCE
locks.Received()[0].CloseAsync(); // Lock must be closed
connector.Command.UpdateLockingStateAsync(bike, Arg.Any());
#endif
bikesViewModel.ActionText = "Connection error on updating locking status.";
bikesViewModel.ActionText = "One moment please...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked Closed" after action
Assert.AreEqual("End rental", subsequent.ButtonText);
Assert.IsTrue(subsequent.IsButtonVisible);
Assert.AreEqual("Open lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
///
/// Use case: close lock
/// Final state: Booked closed.
///
[Test]
public void TestCloseUpdateLockingStateFailsResponseException()
{
var bike = Substitute.For();
var connector = Substitute.For();
var command = Substitute.For();
var geolocation = Substitute.For();
var locks = Substitute.For();
var pollingManager = Substitute.For();
var viewService = Substitute.For();
var bikesViewModel = Substitute.For();
var activeUser = Substitute.For();
var handler = new BookedOpen(
bike,
() => true, // isConnectedDelegate
(isConnexted) => connector,
geolocation,
locks,
() => pollingManager,
Substitute.For(),
viewService,
bikesViewModel,
activeUser);
#if !USELOCALINSTANCE
bike.CloseLockAsync(Arg.Any(), Arg.Any())
.Returns(async x =>
{
// Add calls to ReportStep which IBikeInfoMutable implementation would do.
handler.ReportStep(Step.ClosingLock);
handler.ReportStep(Step.UpdateLockingState);
await handler.ReportStateAsync(CloseCommand.State.ResponseIsInvalid, "Some invalid data received");
}
);
bike.LockInfo.State.Returns(LockingState.Closed);
#else
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."));
#endif
bike.State.Value.Returns(InUseStateEnum.Booked);
var subsequent = handler.HandleRequestOption1().Result;
// 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.ActionText = "The lock bolt moves through the spokes of the rear wheel.";
#if USELOCALINSTANCE
locks.Received()[0].CloseAsync(); // Lock must be closed
connector.Command.UpdateLockingStateAsync(bike, Arg.Any());
#endif
bikesViewModel.ActionText = "Status error on updating lock state.";
bikesViewModel.ActionText = "One moment please...";
pollingManager.StartAsync(); // polling must be restarted again
bikesViewModel.ActionText = string.Empty;
bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
});
// Verify state "Booked Closed" after action
Assert.AreEqual("End rental", subsequent.ButtonText);
Assert.IsTrue(subsequent.IsButtonVisible);
Assert.AreEqual("Open lock", subsequent.LockitButtonText);
Assert.IsTrue(subsequent.IsLockitButtonVisible);
}
}
}