diff --git a/src/flowmeasure/FlowMeasureStatusUpdates.cpp b/src/flowmeasure/FlowMeasureStatusUpdates.cpp index 3cff37b..1774fea 100644 --- a/src/flowmeasure/FlowMeasureStatusUpdates.cpp +++ b/src/flowmeasure/FlowMeasureStatusUpdates.cpp @@ -26,7 +26,7 @@ namespace ECFMP::FlowMeasure { if (!canonicalFlowMeasures.contains(canonicalInformation.Identifier())) { canonicalFlowMeasures[canonicalInformation.Identifier()] = measure; BroadcastStatusUpdate(measure); - return; + continue; } // Switch the measure we have stored out for the new one diff --git a/test/api/FlowMeasureDataParserTest.cpp b/test/api/FlowMeasureDataParserTest.cpp index 2804fe1..d5223c1 100644 --- a/test/api/FlowMeasureDataParserTest.cpp +++ b/test/api/FlowMeasureDataParserTest.cpp @@ -329,6 +329,106 @@ namespace ECFMPTest::Api { } ); + class FlowMeasureDataParserMultipleMeasuresTest : public testing::Test + { + public: + void SetUp() override + { + // Create the mock parsers + auto filterParser = std::make_unique(); + filterParserRaw = filterParser.get(); + auto measureParser = std::make_unique(); + measureParserRaw = measureParser.get(); + + eventBus = ECFMP::EventBus::MakeEventBus(); + mockEventHandler = std::make_shared(2); + mockEventHandlerInternal = std::make_shared(2); + eventBus->SubscribeSync(mockEventHandler); + eventBus->SubscribeSync(mockEventHandlerInternal); + + customFilters = + std::make_shared>>(); + parser = std::make_unique( + std::move(filterParser), std::move(measureParser), std::make_shared(), eventBus, + customFilters + ); + + firs.Add(std::make_shared( + 1, "EGTT", "London" + )); + firs.Add(std::make_shared( + 2, "EGPX", "Scottish" + )); + + events.Add(std::make_shared( + 1, "Test event", now, plusOneHour, firs.Get(1), "abc", + std::vector>{} + )); + } + + std::shared_ptr>> customFilters; + std::shared_ptr mockEventHandler; + std::shared_ptr mockEventHandlerInternal; + std::shared_ptr eventBus; + MockFlowMeasureFilterParser* filterParserRaw; + MockFlowMeasureMeasureParser* measureParserRaw; + ECFMP::Api::InternalEventCollection events; + ECFMP::Api::InternalFlightInformationRegionCollection firs; + std::unique_ptr parser; + }; + + TEST_F(FlowMeasureDataParserMultipleMeasuresTest, ItParsesFlowMeasures) + { + // Mock the measure and filter parser returns + ON_CALL(*filterParserRaw, Parse(testing::_, testing::_)) + .WillByDefault(testing::Invoke([&](const nlohmann::json& data, + const ECFMP::Api::InternalEventCollection& events) { + return std::make_unique( + std::list>(), + std::list>(), + std::list>(), + std::list>(), + std::list>(), + std::list>(), + std::make_shared() + ); + })); + + ON_CALL(*measureParserRaw, Parse(testing::_)).WillByDefault(testing::Invoke([&](const nlohmann::json& data) { + return std::make_unique(ECFMP::FlowMeasure::MeasureType::GroundStop); + })); + + const auto measure1 = nlohmann::json{ + {"id", 1}, + {"event_id", 1}, + {"ident", "EGTT-01A"}, + {"reason", "reason 1"}, + {"starttime", ECFMP::Date::DateStringFromTimePoint(plusOneHour)}, + {"endtime", ECFMP::Date::DateStringFromTimePoint(plusTwoHours)}, + {"withdrawn_at", nlohmann::json::value_t::null}, + {"measure", {{"foo", "bar"}}},// Measure is mocked and handled elsewhere, so placeholder + {"filters", {{"foo", "baz"}}},// Filter is mocked and handled elsewhere, so placeholder + {"notified_flight_information_regions", nlohmann::json::array({1, 2})}}; + + const auto measure2 = nlohmann::json{ + {"id", 2}, + {"event_id", 1}, + {"ident", "EGTT-01B"}, + {"reason", "reason 1"}, + {"starttime", ECFMP::Date::DateStringFromTimePoint(plusOneHour)}, + {"endtime", ECFMP::Date::DateStringFromTimePoint(plusTwoHours)}, + {"withdrawn_at", nlohmann::json::value_t::null}, + {"measure", {{"foo", "bar"}}},// Measure is mocked and handled elsewhere, so placeholder + {"filters", {{"foo", "baz"}}},// Filter is mocked and handled elsewhere, so placeholder + {"notified_flight_information_regions", nlohmann::json::array({1, 2})}}; + + auto data = nlohmann::json{{"flow_measures", nlohmann::json::array({measure1, measure2})}}; + auto flowMeasures = parser->ParseFlowMeasures(data, events, firs); + EXPECT_EQ(2, flowMeasures->Count()); + EXPECT_EQ("EGTT-01A", flowMeasures->begin()->Identifier()); + EXPECT_EQ("EGTT-01B", (++flowMeasures->begin())->Identifier()); + } + struct FlowMeasureDataParserBadDataTestCase { std::string description; nlohmann::json data; diff --git a/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp b/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp index 6785ed6..d230a73 100644 --- a/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp +++ b/test/flowmeasure/FlowMeasureStatusUpdatesTest.cpp @@ -43,6 +43,44 @@ namespace ECFMPTest::FlowMeasure { std::shared_ptr expectedMeasure; }; + template + class FlowMeasureStatusUpdatesMultipleEventListener : public ECFMP::EventBus::EventListener + { + public: + FlowMeasureStatusUpdatesMultipleEventListener( + std::unordered_map> expectedMeasures + ) + : expectedMeasures(std::move(expectedMeasures)) + {} + + void OnEvent(const EventType& event) override + { + callCount++; + EXPECT_TRUE(expectedMeasures.contains(event.flowMeasure->Id())); + expectedMeasures.erase(event.flowMeasure->Id()); + } + + void AssertCalledOnce() + { + EXPECT_EQ(callCount, 1); + } + + void AssertCalledTwice() + { + EXPECT_EQ(callCount, 2); + } + + void AssertNotCalled() + { + EXPECT_EQ(callCount, 0); + } + + private: + int callCount = 0; + + std::unordered_map> expectedMeasures; + }; + class FlowMeasureReissuedEventListener : public ECFMP::EventBus::EventListener { @@ -87,12 +125,13 @@ namespace ECFMPTest::FlowMeasure { {} [[nodiscard]] static auto - GetMeasureMock(const std::string& identifier, const ECFMP::FlowMeasure::MeasureStatus status) + GetMeasureMock(const std::string& identifier, const ECFMP::FlowMeasure::MeasureStatus status, int id = 1) -> std::shared_ptr { auto measure = std::make_shared(); auto canonicalInformation = std::make_shared(identifier); + ON_CALL(*measure, Id).WillByDefault(testing::Return(id)); ON_CALL(*measure, CanonicalInformation) .WillByDefault(testing::Invoke( [canonicalInformation]() -> const ECFMP::FlowMeasure::CanonicalFlowMeasureInfo& { @@ -265,6 +304,52 @@ namespace ECFMPTest::FlowMeasure { listenerExpired->AssertCalledOnce(); } + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsMultipleMeasures) + { + // Set up the measure and collection + auto measure1 = GetMeasureMock("EGTT01A", ECFMP::FlowMeasure::MeasureStatus::Expired); + auto measure2 = GetMeasureMock("EGTT01B", ECFMP::FlowMeasure::MeasureStatus::Expired, 2); + std::shared_ptr flowMeasures = + std::make_shared(); + flowMeasures->Add(measure1); + flowMeasures->Add(measure2); + + // Set up event listeners + auto listenerNotified = + std::make_shared>( + measure1 + ); + auto listenerActivated = + std::make_shared>( + measure1 + ); + auto listenerWithdrawn = + std::make_shared>( + measure1 + ); + + const std::unordered_map> expectedMeasures = { + {measure1->Id(), measure1}, + {measure2->Id(), measure2}}; + auto listenerExpired = + std::make_shared>( + expectedMeasures + ); + + eventBus->SubscribeSync(listenerNotified); + eventBus->SubscribeSync(listenerActivated); + eventBus->SubscribeSync(listenerWithdrawn); + eventBus->SubscribeSync(listenerExpired); + + // Run event and check the events broadcast + EXPECT_EQ(2, flowMeasures->GetUnderlyingCollection().size()); + flowMeasureStatusUpdates->OnEvent({flowMeasures}); + listenerNotified->AssertNotCalled(); + listenerActivated->AssertNotCalled(); + listenerWithdrawn->AssertNotCalled(); + listenerExpired->AssertCalledTwice(); + } + TEST_F(FlowMeasureStatusUpdatesTest, ItBroadcastsReissuedEventOnCanonicalUpodate) { // Set up the measure and collection