mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-05 10:36:30 +01:00
463 lines
18 KiB
C#
463 lines
18 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using NSubstitute;
|
|
using NSubstitute.ExceptionExtensions;
|
|
using NUnit.Framework;
|
|
using ShareeBike.Model;
|
|
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
|
using ShareeBike.Model.Connector;
|
|
using ShareeBike.Model.State;
|
|
using ShareeBike.Model.User;
|
|
using ShareeBike.Repository.Request;
|
|
using ShareeBike.Services.BluetoothLock;
|
|
using ShareeBike.Services.Geolocation;
|
|
using ShareeBike.View;
|
|
using ShareeBike.ViewModel;
|
|
using ShareeBike.ViewModel.Bikes;
|
|
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
|
|
using EndRentalCommand = ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand;
|
|
using DisconnectCommand = ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
|
using ShareeBike.ViewModel.Bikes.Bike;
|
|
using ShareeBike.Repository.Exception;
|
|
|
|
namespace SharedBusinessLogic.Tests.ViewModel.Bikes.Bike
|
|
{
|
|
[TestFixture]
|
|
public class TestEndRentalActionViewModel
|
|
{
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Initial state: No LastGeolocation from CloseLock, Lock currently in reach
|
|
/// Final state: Disposable closed
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnLockInReach()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
var listener = Substitute.For<EndRentalCommand.IEndRentalCommandListener>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Returns(x =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
returnBikeActionViewModel.ReportStep(DisconnectCommand.Step.DisconnectLock);
|
|
return Task.CompletedTask;
|
|
});
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Disposable); // Request handler factory queries state to create appropriate request handler object.
|
|
bike.LockInfo.State.Returns(LockingState.UnknownDisconnected); // Request handler factory queries lock state to create appropriate request handler object.
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
bikesViewModel.ActionText = "Disconnecting lock...";
|
|
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
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Initial state: No LastGeolocation from CloseLock, Lock currently out of reach
|
|
/// Final state: Booked, lock state unknown.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnNoGPSData()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var geolocation = Substitute.For<IGeolocationService>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
var activeUser = Substitute.For<IUser>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Throws((x) =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
returnBikeActionViewModel.ReportStateAsync(EndRentalCommand.State.NoGPSData, "").Wait();
|
|
throw new Exception();
|
|
});
|
|
|
|
NoGPSDataException.IsNoGPSData("exception.", out NoGPSDataException noGPSDataException);
|
|
|
|
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw noGPSDataException);
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Booked);
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
//viewService.DisplayAlert(
|
|
// "Rental could not be terminated!",
|
|
// "We could not assign the bike to any station. For this we need your location information while you are standing right next to the bike. Only then your rental can be terminated!\r\n\r\nApproach the bike lock, turn on Bluetooth and Location services and try again.",
|
|
// "OK");
|
|
|
|
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 after action
|
|
Assert.That(
|
|
bike.LockInfo.State,
|
|
Is.EqualTo(LockingState.UnknownDisconnected));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Final state: Same as initial state.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnReturnFailsWebConnectFailureException()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Returns(async x =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
await returnBikeActionViewModel.ReportStateAsync(EndRentalCommand.State.WebConnectFailed, "");
|
|
throw new Exception();
|
|
});
|
|
|
|
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new Exception("Exception message."));
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Booked); // Request handler factory queries state to create appropriate request handler object.
|
|
bike.LockInfo.State.Returns(LockingState.Closed); // Request handler factory queries lock state to create appropriate request handler object.
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
//await locks.DidNotReceive().DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
viewService.DisplayAlert(
|
|
"Rental could not be terminated!",
|
|
"A stable Internet connection is required. Connect to WIFI or to mobile network and activate mobile data. Try again.",
|
|
"OK"
|
|
);
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Final state: Same as initial state.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnReturnFailsNotAtStationException()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Returns(async x =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
await returnBikeActionViewModel.ReportStateAsync(EndRentalCommand.State.NotAtStation, "");
|
|
throw new Exception();
|
|
});
|
|
|
|
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new Exception("Exception message."));
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Booked); // Request handler factory queries state to create appropriate request handler object.
|
|
bike.LockInfo.State.Returns(LockingState.Closed); // Request handler factory queries lock state to create appropriate request handler object.
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
//viewService.DisplayAlert(
|
|
// "Rental could not be terminated!",
|
|
// "We could not assign the bike to any station. For this we need your location information while you are standing right next to the bike. Only then your rental can be terminated!\r\n\r\nApproach the bike lock, turn on Bluetooth and Location services and try again.",
|
|
// "OK"
|
|
//);
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Initial state: Copri reports that no GPS data available
|
|
/// Final state: Same as initial state.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnReturnFailsNoGPSDataException()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Returns(async x =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
await returnBikeActionViewModel.ReportStateAsync(EndRentalCommand.State.NoGPSData, "");
|
|
throw new Exception();
|
|
});
|
|
|
|
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new Exception("Exception message."));
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Booked); // Request handler factory queries state to create appropriate request handler object.
|
|
bike.LockInfo.State.Returns(LockingState.Closed); // Request handler factory queries lock state to create appropriate request handler object.
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
viewService.DisplayAlert(
|
|
"Rental could not be terminated!",
|
|
"End rental at an unknown location is not possible.\r\nRental can be ended if\r\n- location information is available when closing lock\r\n- bike is in reach and location information is available when pressing button \"End rental\"",
|
|
"OK"
|
|
);
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Final state: Same as initial state.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnReturnFailsResponseException()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Returns(async x =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
await returnBikeActionViewModel.ReportStateAsync(EndRentalCommand.State.ResponseException, "");
|
|
throw new Exception();
|
|
});
|
|
|
|
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new Exception("Exception message."));
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Booked); // Request handler factory queries state to create appropriate request handler object.
|
|
bike.LockInfo.State.Returns(LockingState.Closed); // Request handler factory queries lock state to create appropriate request handler object.
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
viewService.DisplayAlert(
|
|
"Rental could not be terminated!",
|
|
"Connection error, invalid server response.",
|
|
"OK"
|
|
);
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use case: End rental.
|
|
/// Final state: Same as initial state.
|
|
/// </summary>
|
|
[Test]
|
|
public async Task TestReturnReturnFailsException()
|
|
{
|
|
var bike = Substitute.For<IBikeInfoMutable>();
|
|
var connector = Substitute.For<IConnector>();
|
|
var command = Substitute.For<ICommand>();
|
|
var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
|
|
var viewService = Substitute.For<IViewService>();
|
|
var bikesViewModel = Substitute.For<IBikesViewModel>();
|
|
|
|
var returnBikeActionViewModel = new EndRentalActionViewModel<BookedClosed>(
|
|
bike,
|
|
() => true, // isConnectedDelegate
|
|
(isConnected) => connector,
|
|
() => pollingManager,
|
|
viewService,
|
|
bikesViewModel);
|
|
|
|
bike.Id.Returns("0");
|
|
|
|
bike.ReturnBikeAsync(Arg.Any<EndRentalCommand.IEndRentalCommandListener>()).Returns(async x =>
|
|
{
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.GetLocation);
|
|
returnBikeActionViewModel.ReportStep(EndRentalCommand.Step.ReturnBike);
|
|
await returnBikeActionViewModel.ReportStateAsync(EndRentalCommand.State.GeneralEndRentalError, "");
|
|
throw new Exception();
|
|
});
|
|
|
|
connector.Command.DoReturn(bike, Arg.Any<LocationDto>()).Returns<BookingFinishedModel>(x => throw new Exception("Exception message."));
|
|
|
|
bike.State.Value.Returns(InUseStateEnum.Booked); // Request handler factory queries state to create appropriate request handler object.
|
|
bike.LockInfo.State.Returns(LockingState.Closed); // Request handler factory queries lock state to create appropriate request handler object.
|
|
|
|
await returnBikeActionViewModel.EndRentalAsync();
|
|
|
|
//await locks.DidNotReceive().DisconnectAsync(Arg.Any<int>(), Arg.Any<Guid>());
|
|
|
|
// 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 = "Query location...";
|
|
bikesViewModel.ActionText = "Ending rental...";
|
|
viewService.DisplayAlert(
|
|
"Rental could not be terminated!",
|
|
"Please try again.",
|
|
"OK"
|
|
);
|
|
|
|
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
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|