From 57dd53ceeb6c552b84a1ec778bdda2cc88f232a0 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Wed, 20 Dec 2023 19:38:30 +0000 Subject: [PATCH 1/2] fix: curl requests not sending bodies --- src/utils/api/ApiCurlRequestFactory.cpp | 4 ++++ src/utils/api/CurlApiRequestPerformer.cpp | 8 ++++++++ test/testingutils/test/ApiExpectation.cpp | 6 ++++++ test/testingutils/test/ApiExpectation.h | 1 + test/testingutils/test/ApiResponseExpectation.h | 1 + test/utils/api/ApiCurlRequestFactoryTest.cpp | 15 +++++++++++++++ 6 files changed, 35 insertions(+) diff --git a/src/utils/api/ApiCurlRequestFactory.cpp b/src/utils/api/ApiCurlRequestFactory.cpp index f65128ea6..f14cde4a8 100644 --- a/src/utils/api/ApiCurlRequestFactory.cpp +++ b/src/utils/api/ApiCurlRequestFactory.cpp @@ -16,6 +16,10 @@ namespace UKControllerPluginUtils::Api { auto ApiCurlRequestFactory::BuildCurlRequest(const ApiRequestData& data) const -> CurlRequest { CurlRequest request(urlBuilder.BuildUrl(data), data.Method()); + if (!data.Body().empty()) { + request.SetBody(data.Body().dump()); + } + headerApplicator.ApplyHeaders(request); return request; } diff --git a/src/utils/api/CurlApiRequestPerformer.cpp b/src/utils/api/CurlApiRequestPerformer.cpp index cdaaf40e5..9f7a62b89 100644 --- a/src/utils/api/CurlApiRequestPerformer.cpp +++ b/src/utils/api/CurlApiRequestPerformer.cpp @@ -20,8 +20,16 @@ namespace UKControllerPluginUtils::Api { auto CurlApiRequestPerformer::Perform(const ApiRequestData& data) -> Response { + const auto request = requestFactory.BuildCurlRequest(data); + LogDebug( + "CurlApiRequestPerformer: Performing cURL request with method " + std::string(data.Method()) + " to " + + data.Uri() + " with body " + data.Body().dump()); + auto curlResponse = curl.MakeCurlRequest(requestFactory.BuildCurlRequest(data)); if (!ResponseSuccessful(curlResponse)) { + LogDebug( + "CurlApiRequestPerformer: Failed cURL request, status was: " + + std::to_string(curlResponse.GetStatusCode()) + " response body was " + curlResponse.GetResponse()); throw ApiRequestException(data.Uri(), static_cast(curlResponse.GetStatusCode()), false); } diff --git a/test/testingutils/test/ApiExpectation.cpp b/test/testingutils/test/ApiExpectation.cpp index 98cb0cf3a..62e01ce16 100644 --- a/test/testingutils/test/ApiExpectation.cpp +++ b/test/testingutils/test/ApiExpectation.cpp @@ -77,6 +77,12 @@ namespace UKControllerPluginTest { return *this; } + ApiResponseExpectation& ApiExpectation::WillReturnCreated() + { + this->responseCode = HttpStatusCode::Created; + return *this; + } + ApiResponseExpectation& ApiExpectation::WillReturnOk() { this->responseCode = HttpStatusCode::Ok; diff --git a/test/testingutils/test/ApiExpectation.h b/test/testingutils/test/ApiExpectation.h index d3810f3fb..3aaa51d4d 100644 --- a/test/testingutils/test/ApiExpectation.h +++ b/test/testingutils/test/ApiExpectation.h @@ -35,6 +35,7 @@ namespace UKControllerPluginTest { auto Delete() -> ApiUriExpectation& override; auto WithBody(const nlohmann::json& body) -> ApiResponseExpectation& override; auto WithoutBody() -> ApiResponseExpectation& override; + auto WillReturnCreated() -> ApiResponseExpectation& override; auto WillReturnOk() -> ApiResponseExpectation& override; auto WillReturnServerError() -> ApiResponseExpectation& override; auto WillReturnForbidden() -> ApiResponseExpectation& override; diff --git a/test/testingutils/test/ApiResponseExpectation.h b/test/testingutils/test/ApiResponseExpectation.h index b8511b4cd..bff9169e8 100644 --- a/test/testingutils/test/ApiResponseExpectation.h +++ b/test/testingutils/test/ApiResponseExpectation.h @@ -7,6 +7,7 @@ namespace UKControllerPluginTest { class ApiResponseExpectation { public: + virtual auto WillReturnCreated() -> ApiResponseExpectation& = 0; virtual auto WillReturnOk() -> ApiResponseExpectation& = 0; virtual auto WillReturnServerError() -> ApiResponseExpectation& = 0; virtual auto WillReturnForbidden() -> ApiResponseExpectation& = 0; diff --git a/test/utils/api/ApiCurlRequestFactoryTest.cpp b/test/utils/api/ApiCurlRequestFactoryTest.cpp index 507ce121f..1f1fec98d 100644 --- a/test/utils/api/ApiCurlRequestFactoryTest.cpp +++ b/test/utils/api/ApiCurlRequestFactoryTest.cpp @@ -38,4 +38,19 @@ namespace UKControllerPluginUtilsTest::Api { EXPECT_EQ(expectedRequest, request); } + + TEST_F(ApiCurlRequestFactoryTest, ItBuildsRequestsWithABody) + { + const auto body = nlohmann::json{{"test", "test"}}; + auto request = requestFactory.BuildCurlRequest( + ApiRequestData("test", UKControllerPluginUtils::Http::HttpMethod::Post(), body)); + + CurlRequest expectedRequest("https://ukcp.vatsim.uk/api/test", CurlRequest::METHOD_POST); + expectedRequest.AddHeader("Authorization", "Bearer key"); + expectedRequest.AddHeader("Accept", "application/json"); + expectedRequest.AddHeader("Content-Type", "application/json"); + expectedRequest.SetBody(body.dump()); + + EXPECT_EQ(expectedRequest, request); + } } // namespace UKControllerPluginUtilsTest::Api From 7c1efbf604566c57728f9c02eb2b445c17b2ecbe Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Wed, 20 Dec 2023 19:39:22 +0000 Subject: [PATCH 2/2] feat: api stand auto assignments --- src/plugin/stands/StandEventHandler.cpp | 146 ++- src/plugin/stands/StandEventHandler.h | 24 +- src/plugin/stands/StandModule.cpp | 1 + test/plugin/stands/StandEventHandlerTest.cpp | 1010 +++++++++++++++++- test/plugin/stands/StandModuleTest.cpp | 3 + 5 files changed, 1159 insertions(+), 25 deletions(-) diff --git a/src/plugin/stands/StandEventHandler.cpp b/src/plugin/stands/StandEventHandler.cpp index 972d25d51..b74df94c1 100644 --- a/src/plugin/stands/StandEventHandler.cpp +++ b/src/plugin/stands/StandEventHandler.cpp @@ -3,10 +3,13 @@ #include "StandUnassignedMessage.h" #include "api/ApiException.h" #include "api/ApiInterface.h" +#include "api/ApiRequestFactory.h" +#include "api/ApiRequestException.h" #include "euroscope/EuroScopeCControllerInterface.h" #include "euroscope/EuroScopeCFlightPlanInterface.h" #include "euroscope/EuroScopeCRadarTargetInterface.h" #include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "tag/TagData.h" #include "task/TaskRunnerInterface.h" @@ -27,11 +30,14 @@ namespace UKControllerPlugin::Stands { TaskRunnerInterface& taskRunner, EuroscopePluginLoopbackInterface& plugin, Integration::OutboundIntegrationEventHandler& integrationEventHandler, + std::shared_ptr ownership, std::set stands, int standSelectedCallbackId) : api(api), taskRunner(taskRunner), plugin(plugin), stands(std::move(stands)), - integrationEventHandler(integrationEventHandler), standSelectedCallbackId(standSelectedCallbackId) + integrationEventHandler(integrationEventHandler), ownership(ownership), + standSelectedCallbackId(standSelectedCallbackId) { + assert(this->ownership != nullptr && "Ownership must not be null"); } /* @@ -84,30 +90,43 @@ namespace UKControllerPlugin::Stands { } }); } else { - // Find the requested stand - auto stand = std::find_if( - this->stands.cbegin(), this->stands.cend(), [airfield, identifier](const Stand& stand) -> bool { - return stand.identifier == identifier && stand.airfieldCode == airfield; - }); - - if (stand == this->stands.cend()) { - LogInfo("Tried to assign a non-existant stand"); - return "Tried to assign a non-existant stand"; + if (identifier != this->autoStandMenuItem) { + return this->AssignStandInApi(callsign, airfield, identifier); } - // Assign that stand - this->AssignStandToAircraft(callsign, *stand); + this->RequestStandFromApi(*aircraft); + } - int standId = stand->id; - this->taskRunner.QueueAsynchronousTask([this, standId, callsign]() { - try { - this->api.AssignStandToAircraft(callsign, standId); - } catch (ApiException&) { - LogError("Failed to create stand assignment for " + callsign); - } + return ""; + } + + auto StandEventHandler::AssignStandInApi( + const std::string& callsign, const std::string& airfield, const std::string& identifier) -> std::string + { + // Find the requested stand + auto stand = std::find_if( + this->stands.cbegin(), this->stands.cend(), [airfield, identifier](const Stand& stand) -> bool { + return stand.identifier == identifier && stand.airfieldCode == airfield; }); + + if (stand == this->stands.cend()) { + LogInfo("Tried to assign a non-existant stand"); + return "Tried to assign a non-existant stand"; } + // Assign that stand + this->AssignStandToAircraft(callsign, *stand); + + int standId = stand->id; + auto callsignForRequest = callsign; + this->taskRunner.QueueAsynchronousTask([this, standId, callsignForRequest]() { + try { + this->api.AssignStandToAircraft(callsignForRequest, standId); + } catch (ApiException&) { + LogError("Failed to create stand assignment for " + callsignForRequest); + } + }); + return ""; } @@ -157,6 +176,12 @@ namespace UKControllerPlugin::Stands { this->plugin.AddItemToPopupList(menuItem); } + // Add the penultimate item, "AUTO" in a fixed position + menuItem.firstValue = this->autoStandMenuItem; + menuItem.secondValue = "Automatic"; + menuItem.fixedPosition = true; + this->plugin.AddItemToPopupList(menuItem); + // Add the final item, no stand, in a fixed position menuItem.firstValue = this->noStandMenuItem; menuItem.secondValue = "None"; @@ -416,7 +441,7 @@ namespace UKControllerPlugin::Stands { return {{PushEventSubscription::SUB_TYPE_CHANNEL, "private-stand-assignments"}}; } - auto StandEventHandler::LockStandMap() -> std::lock_guard + auto StandEventHandler::LockStandMap() -> std::lock_guard { return std::lock_guard(this->mapMutex); } @@ -502,4 +527,85 @@ namespace UKControllerPlugin::Stands { success(); } } + + void StandEventHandler::RequestStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan) + { + // If EuroScope wont tell us where they are, do nothing. + if (flightplan.GetDistanceFromOrigin() == 0.0) { + return; + } + + // If they're close to departure and we're providing delivery, assign a stand + if (flightplan.GetDistanceFromOrigin() < this->maxDistanceForDepartureStands && + this->ownership->DeliveryControlProvidedByUser(flightplan.GetOrigin())) { + this->RequestDepartureStandFromApi(flightplan); + } + + // Otherwise, assume they're close to arrival + if (!this->ownership->DeliveryControlProvidedByUser(flightplan.GetDestination())) { + return; + } + + this->RequestArrivalStandFromApi(flightplan); + } + void StandEventHandler::RequestDepartureStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan) + { + const auto radarTarget = this->plugin.GetRadarTargetForCallsign(flightplan.GetCallsign()); + if (!radarTarget) { + return; + } + + this->DoApiStandRequest( + flightplan.GetCallsign(), + { + {"callsign", flightplan.GetCallsign()}, + {"departure_airfield", flightplan.GetOrigin()}, + {"assignment_type", "departure"}, + {"latitude", radarTarget->GetPosition().m_Latitude}, + {"longitude", radarTarget->GetPosition().m_Longitude}, + }); + } + + void StandEventHandler::RequestArrivalStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan) + { + this->DoApiStandRequest( + flightplan.GetCallsign(), + {{"callsign", flightplan.GetCallsign()}, + {"departure_airfield", flightplan.GetOrigin()}, + {"arrival_airfield", flightplan.GetDestination()}, + {"assignment_type", "arrival"}, + {"aircraft_type", flightplan.GetAircraftType()}}); + } + + void StandEventHandler::DoApiStandRequest(const std::string& callsign, const nlohmann::json data) + { + LogDebug("Requesting stand assignment from API: " + data.dump()); + const std::string requestCallsign = callsign; + ApiRequest() + .Post("stand/assignment/requestauto", data) + .Then([this, requestCallsign](const UKControllerPluginUtils::Api::Response& response) { + auto lock = this->LockStandMap(); + + const auto& data = response.Data(); + if (!data.contains("stand_id") || !data.at("stand_id").is_number_integer()) { + LogWarning("Invalid stand assignment response " + data.dump()); + return; + } + + const auto standId = data.at("stand_id").get(); + if (!this->stands.contains(standId)) { + LogWarning("Invalid stand assignment response, bad id " + data.dump()); + return; + } + + const auto& stand = this->stands.find(standId); + LogInfo("API generated stand assignment " + std::to_string(standId) + " for " + requestCallsign); + + this->AssignStandToAircraft(requestCallsign, *stand); + }) + .Catch([requestCallsign](const UKControllerPluginUtils::Api::ApiRequestException& exception) { + LogError("Failed to request stand assignment for " + requestCallsign + ": " + exception.what()); + }); + } + } // namespace UKControllerPlugin::Stands diff --git a/src/plugin/stands/StandEventHandler.h b/src/plugin/stands/StandEventHandler.h index 8d9a71589..0c4981986 100644 --- a/src/plugin/stands/StandEventHandler.h +++ b/src/plugin/stands/StandEventHandler.h @@ -15,6 +15,9 @@ namespace UKControllerPlugin { namespace Euroscope { class EuroscopePluginLoopbackInterface; } // namespace Euroscope + namespace Ownership { + class AirfieldServiceProviderCollection; + } // namespace Ownership namespace TaskManager { class TaskRunnerInterface; } // namespace TaskManager @@ -36,6 +39,7 @@ namespace UKControllerPlugin::Stands { TaskManager::TaskRunnerInterface& taskRunner, Euroscope::EuroscopePluginLoopbackInterface& plugin, Integration::OutboundIntegrationEventHandler& integrationEventHandler, + std::shared_ptr ownership, std::set stands, int standSelectedCallbackId); [[nodiscard]] auto ActionsToProcess() const -> std::vector override; @@ -91,6 +95,13 @@ namespace UKControllerPlugin::Stands { private: void AssignStandToAircraft(const std::string& callsign, const Stand& stand); + [[nodiscard]] auto + AssignStandInApi(const std::string& callsign, const std::string& airfield, const std::string& identifier) + -> std::string; + void RequestStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan); + void RequestDepartureStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan); + void RequestArrivalStandFromApi(const Euroscope::EuroScopeCFlightPlanInterface& flightplan); + void DoApiStandRequest(const std::string& callsign, const nlohmann::json data); void UnassignStandForAircraft(const std::string& callsign); [[nodiscard]] auto AssignmentMessageValid(const nlohmann::json& message) const -> bool; auto CanAssignStand(UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> bool; @@ -98,7 +109,7 @@ namespace UKControllerPlugin::Stands { auto GetAirfieldForStandAssignment(UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> std::string; - auto LockStandMap() -> std::lock_guard; + auto LockStandMap() -> std::lock_guard; // The last airfield that was used to populate the stand menu, used when we receive the callback std::string lastAirfieldUsed; @@ -119,17 +130,23 @@ namespace UKControllerPlugin::Stands { std::map standAssignments; // Locks the stand assignments map to prevent concurrent edits - std::mutex mapMutex; + std::recursive_mutex mapMutex; // Allows us to send events to our integrations Integration::OutboundIntegrationEventHandler& integrationEventHandler; + // The ownership of airfields + std::shared_ptr ownership; + // The id for the callback when a stand is selected const int standSelectedCallbackId; // Max distance from departure airport to decide whether we show departure or arrival airport stands const int maxDistanceFromDepartureAirport = 7; + // The menu item to display for "auto" stand + const std::string autoStandMenuItem = "AUTO"; + // The menu item to display for no assigned stand const std::string noStandMenuItem = "--"; @@ -138,5 +155,8 @@ namespace UKControllerPlugin::Stands { // Annotation box 4 const int annotationIndex = 3; + + // Max distance from origin to do departure stands + const double maxDistanceForDepartureStands = 3.5; }; } // namespace UKControllerPlugin::Stands diff --git a/src/plugin/stands/StandModule.cpp b/src/plugin/stands/StandModule.cpp index afa9141cd..d42f41c7f 100644 --- a/src/plugin/stands/StandModule.cpp +++ b/src/plugin/stands/StandModule.cpp @@ -40,6 +40,7 @@ namespace UKControllerPlugin::Stands { *container.taskRunner, *container.plugin, *container.integrationModuleContainer->outboundMessageHandler, + container.airfieldOwnership, stands, standSelectedCallbackId); diff --git a/test/plugin/stands/StandEventHandlerTest.cpp b/test/plugin/stands/StandEventHandlerTest.cpp index b724fe652..2bdf46e72 100644 --- a/test/plugin/stands/StandEventHandlerTest.cpp +++ b/test/plugin/stands/StandEventHandlerTest.cpp @@ -1,6 +1,10 @@ #include "stands/StandEventHandler.h" #include "tag/TagData.h" #include "api/ApiException.h" +#include "controller/ActiveCallsign.h" +#include "controller/ControllerPosition.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" #include "stands/StandUnassignedMessage.h" #include "stands/StandAssignedMessage.h" #include "integration/InboundMessage.h" @@ -33,12 +37,19 @@ using UKControllerPluginTest::TaskManager::MockTaskRunnerInterface; namespace UKControllerPluginTest { namespace Stands { - class StandEventHandlerTest : public Test + class StandEventHandlerTest : public ApiTestCase { public: StandEventHandlerTest() - : tagData(flightplan, radarTarget, 110, 1, itemString, &euroscopeColourCode, &tagColour, &fontSize), - handler(api, taskRunner, plugin, mockIntegration, GetStands(), 1) + : controller(2, "LON_S_CTR", 129.420, std::vector{"EGKK"}, true, false, true), + userCallsign(std::make_shared( + "LON_S_CTR", "Test", controller, true)), + notUserCallsign(std::make_shared( + "LON_S_CTR", "Test", controller, false)), + airfieldOwnership( + std::make_shared()), + tagData(flightplan, radarTarget, 110, 1, itemString, &euroscopeColourCode, &tagColour, &fontSize), + handler(api, taskRunner, plugin, mockIntegration, airfieldOwnership, GetStands(), 1) { ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); @@ -65,6 +76,10 @@ namespace UKControllerPluginTest { NiceMock flightplan; NiceMock radarTarget; std::shared_ptr> mockController; + UKControllerPlugin::Controller::ControllerPosition controller; + std::shared_ptr userCallsign; + std::shared_ptr notUserCallsign; + std::shared_ptr airfieldOwnership; TagData tagData; StandEventHandler handler; }; @@ -537,6 +552,14 @@ namespace UKControllerPluginTest { menuItemStand2.disabled = false; menuItemStand2.fixedPosition = false; + PopupMenuItem menuItemStandAuto; + menuItemStandAuto.firstValue = "AUTO"; + menuItemStandAuto.secondValue = "Automatic"; + menuItemStandAuto.callbackFunctionId = 1; + menuItemStandAuto.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + menuItemStandAuto.disabled = false; + menuItemStandAuto.fixedPosition = true; + PopupMenuItem menuItemStandNone; menuItemStandNone.firstValue = "--"; menuItemStandNone.secondValue = "None"; @@ -567,6 +590,8 @@ namespace UKControllerPluginTest { EXPECT_CALL(this->plugin, AddItemToPopupList(menuItemStand2)).Times(1); + EXPECT_CALL(this->plugin, AddItemToPopupList(menuItemStandAuto)).Times(1); + EXPECT_CALL(this->plugin, AddItemToPopupList(menuItemStandNone)).Times(1); this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); @@ -582,6 +607,14 @@ namespace UKControllerPluginTest { menuItemStand.disabled = false; menuItemStand.fixedPosition = false; + PopupMenuItem menuItemStandAuto; + menuItemStandAuto.firstValue = "AUTO"; + menuItemStandAuto.secondValue = "Automatic"; + menuItemStandAuto.callbackFunctionId = 1; + menuItemStandAuto.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + menuItemStandAuto.disabled = false; + menuItemStandAuto.fixedPosition = true; + PopupMenuItem menuItemStandNone; menuItemStandNone.firstValue = "--"; menuItemStandNone.secondValue = "None"; @@ -612,6 +645,8 @@ namespace UKControllerPluginTest { EXPECT_CALL(this->plugin, AddItemToPopupList(menuItemStand)).Times(1); + EXPECT_CALL(this->plugin, AddItemToPopupList(menuItemStandAuto)).Times(1); + EXPECT_CALL(this->plugin, AddItemToPopupList(menuItemStandNone)).Times(1); this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); @@ -1732,5 +1767,974 @@ namespace UKControllerPluginTest { EXPECT_FALSE(failureMessages.empty()); EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); } + + TEST_F(StandEventHandlerTest, ItRequestsADepartureStandFromApi) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"assignment_type", "departure"}, + {"latitude", 123}, + {"longitude", 456}, + }) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"stand_id", 3}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(3, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesNonCreatedCode) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"assignment_type", "departure"}, + {"latitude", 123}, + {"longitude", 456}, + }) + .WillReturnForbidden(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesUnknownStandId) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"assignment_type", "departure"}, + {"latitude", 123}, + {"longitude", 456}, + }) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"stand_id", 999}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesStandIdNotInteger) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"assignment_type", "departure"}, + {"latitude", 123}, + {"longitude", 456}, + }) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"stand_id", "abc"}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesNoStandIdInJson) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"assignment_type", "departure"}, + {"latitude", 123}, + {"longitude", 456}, + }) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"not_stand_id", 3}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesNotFoundRadarTarget) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(nullptr)); + + this->ExpectNoApiRequests(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesUnknownDistanceFromOrigin) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(0.0)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(0.0)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectNoApiRequests(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, DepartureStandRequestsHandlesUserNotProvidingDelivery) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Ground, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGKK", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + std::shared_ptr> pluginReturnedRt = + std::make_shared>(); + + EuroScopePlugIn::CPosition position; + position.m_Latitude = 123; + position.m_Longitude = 456; + ON_CALL(*pluginReturnedRt, GetPosition()).WillByDefault(Return(position)); + + ON_CALL(this->plugin, GetRadarTargetForCallsign("BAW123")).WillByDefault(Return(pluginReturnedRt)); + + this->ExpectNoApiRequests(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ItRequestsAnArrivalStandFromApi) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(55)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"arrival_airfield", "EGGW"}, + {"assignment_type", "arrival"}, + {"aircraft_type", "B738"}}) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"stand_id", 3}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(3, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ArrivalStandRequestHandlesInvalidStatusCodeInResponse) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(55)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"arrival_airfield", "EGGW"}, + {"assignment_type", "arrival"}, + {"aircraft_type", "B738"}}) + .WillReturnServerError(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ArrivalStandRequestHandlesInvalidStandIdInJson) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(55)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"arrival_airfield", "EGGW"}, + {"assignment_type", "arrival"}, + {"aircraft_type", "B738"}}) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"stand_id", 999}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ArrivalStandRequestHandlesNonIntegerStandIdInJson) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(55)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"arrival_airfield", "EGGW"}, + {"assignment_type", "arrival"}, + {"aircraft_type", "B738"}}) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"stand_id", "abc"}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ArrivalStandRequestHandlesNoStandIdInJson) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(55)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectApiRequest() + ->Post() + .To("stand/assignment/requestauto") + .WithBody(nlohmann::json{ + {"callsign", "BAW123"}, + {"departure_airfield", "EGKK"}, + {"arrival_airfield", "EGGW"}, + {"assignment_type", "arrival"}, + {"aircraft_type", "B738"}}) + .WillReturnCreated() + .WithResponseBody(nlohmann::json{{"not_stand_id", 3}}); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ArrivalStandRequestHandlesZeroDistanceToOrigin) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Delivery, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(0.0)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectNoApiRequests(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } + + TEST_F(StandEventHandlerTest, ArrivalStandRequestHandlesUserNotProvidingDelivery) + { + // Make "us" the delivery controller + std::vector> provisions; + provisions.push_back(std::make_shared( + UKControllerPlugin::Ownership::ServiceType::Ground, userCallsign)); + airfieldOwnership->SetProvidersForAirfield("EGGW", provisions); + + // Trigger the menu first to set the last airport + ON_CALL(this->flightplan, IsTracked()).WillByDefault(Return(false)); + + ON_CALL(this->flightplan, IsTrackedByUser()).WillByDefault(Return(false)); + + ON_CALL(this->plugin, GetUserControllerObject()).WillByDefault(Return(this->mockController)); + + ON_CALL(*this->mockController, IsVatsimRecognisedController()).WillByDefault(Return(true)); + + ON_CALL(this->flightplan, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(this->flightplan, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(this->flightplan, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->flightplan, GetDistanceFromOrigin()).WillByDefault(Return(1)); + + this->handler.DisplayStandSelectionMenu(this->flightplan, this->radarTarget, "", {0, 0}); + + std::shared_ptr> pluginReturnedFp = + std::make_shared>(); + + ON_CALL(*pluginReturnedFp, IsTracked()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, IsTrackedByUser()).WillByDefault(Return(true)); + + ON_CALL(*pluginReturnedFp, GetCallsign()).WillByDefault(Return("BAW123")); + + ON_CALL(*pluginReturnedFp, GetDistanceFromOrigin()).WillByDefault(Return(55)); + + ON_CALL(*pluginReturnedFp, GetAircraftType()).WillByDefault(Return("B738")); + + ON_CALL(*pluginReturnedFp, GetOrigin()).WillByDefault(Return("EGKK")); + + ON_CALL(*pluginReturnedFp, GetDestination()).WillByDefault(Return("EGGW")); + + ON_CALL(this->plugin, GetSelectedFlightplan()).WillByDefault(Return(pluginReturnedFp)); + + this->ExpectNoApiRequests(); + + this->handler.StandSelected(1, "AUTO", {}); + + this->AwaitApiCallCompletion(); + EXPECT_EQ(this->handler.noStandAssigned, this->handler.GetAssignedStandForCallsign("BAW123")); + } } // namespace Stands } // namespace UKControllerPluginTest diff --git a/test/plugin/stands/StandModuleTest.cpp b/test/plugin/stands/StandModuleTest.cpp index b160488d8..d7becd003 100644 --- a/test/plugin/stands/StandModuleTest.cpp +++ b/test/plugin/stands/StandModuleTest.cpp @@ -5,6 +5,7 @@ #include "integration/InboundIntegrationMessageHandler.h" #include "integration/IntegrationPersistenceContainer.h" #include "integration/IntegrationServer.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "plugin/FunctionCallEventHandler.h" #include "push/PushEventProcessorCollection.h" #include "stands/StandModule.h" @@ -41,6 +42,8 @@ namespace UKControllerPluginTest::Stands { std::make_unique(nullptr, nullptr, nullptr, nullptr); container.integrationModuleContainer->inboundMessageHandler = std::make_shared(nullptr); + container.airfieldOwnership = + std::make_shared(); nlohmann::json gatwick = nlohmann::json::array(); gatwick.push_back({