From f378837e9e55ef6f64e8aea76f65fb65b5a173a2 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 26 Aug 2025 16:47:24 +0200 Subject: [PATCH 1/2] Added AssetAdded to the agent for 2.6 --- src/mtconnect/agent.cpp | 39 +- .../device_model/data_item/data_item.cpp | 2 + .../device_model/data_item/data_item.hpp | 4 +- src/mtconnect/device_model/device.cpp | 2 + src/mtconnect/device_model/device.hpp | 2 + src/mtconnect/observation/observation.cpp | 1 + src/mtconnect/pipeline/response_document.cpp | 2 +- .../validation/observation_validations.hpp | 3 +- test_package/CMakeLists.txt | 1 + test_package/agent_test.cpp | 937 +----------------- test_package/test_utilities.hpp | 22 +- 11 files changed, 74 insertions(+), 941 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 49182279c..051f8e5a7 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -358,7 +358,7 @@ namespace mtconnect { if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) asset->addHash(); - m_assetStorage->addAsset(asset); + auto old = m_assetStorage->addAsset(asset); for (auto &sink : m_sinks) sink->publish(asset); @@ -368,8 +368,10 @@ namespace mtconnect { DataItemPtr di; if (asset->isRemoved()) di = device->getAssetRemoved(); - else + else if (old || !device->getAssetAdded()) di = device->getAssetChanged(); + else if (device->getAssetAdded()) + di = device->getAssetAdded(); if (di) { entity::Properties props {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}; @@ -763,13 +765,25 @@ namespace mtconnect { { m_loopback->receive(dev->getAssetRemoved(), {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}); - - auto changed = dev->getAssetChanged(); - auto last = getLatest(changed); - if (last && asset->getAssetId() == last->getValue()) { - m_loopback->receive(changed, {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); + auto changed = dev->getAssetChanged(); + auto last = getLatest(changed); + if (last && asset->getAssetId() == last->getValue()) + { + m_loopback->receive(changed, {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); + } + } + + auto added = dev->getAssetAdded(); + if (added) + { + auto last = getLatest(added); + if (last && asset->getAssetId() == last->getValue()) + { + m_loopback->receive(added, {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); + } } + } } } @@ -904,6 +918,17 @@ namespace mtconnect { device->addDataItem(di, errors); } + if (!device->getAssetAdded() && m_intSchemaVersion >= SCHEMA_VERSION(2, 6)) + { + // Create asset removed data item and add it to the device. + entity::ErrorList errors; + auto di = DataItem::make({{"type", "ASSET_ADDED"s}, + {"id", device->getId() + "_asset_add"}, {"discrete", true}, + {"category", "EVENT"s}}, + errors); + device->addDataItem(di, errors); + } + if (!device->getAssetCount() && m_intSchemaVersion >= SCHEMA_VERSION(2, 0)) { entity::ErrorList errors; diff --git a/src/mtconnect/device_model/data_item/data_item.cpp b/src/mtconnect/device_model/data_item/data_item.cpp index 50c0c601a..c3007387e 100644 --- a/src/mtconnect/device_model/data_item/data_item.cpp +++ b/src/mtconnect/device_model/data_item/data_item.cpp @@ -151,6 +151,8 @@ namespace mtconnect { m_specialClass = ASSET_REMOVED_CLS; else if (type == "ASSET_CHANGED") m_specialClass = ASSET_CHANGED_CLS; + else if (type == "ASSET_ADDED") + m_specialClass = ASSET_ADDED_CLS; } else if (category == "CONDITION") { diff --git a/src/mtconnect/device_model/data_item/data_item.hpp b/src/mtconnect/device_model/data_item/data_item.hpp index 8a7c6959c..65f769807 100644 --- a/src/mtconnect/device_model/data_item/data_item.hpp +++ b/src/mtconnect/device_model/data_item/data_item.hpp @@ -75,7 +75,8 @@ namespace mtconnect { THREE_SPACE_CLS, NONE_CLS, ASSET_REMOVED_CLS, - ASSET_CHANGED_CLS + ASSET_CHANGED_CLS, + ASSET_ADDED_CLS }; public: @@ -164,6 +165,7 @@ namespace mtconnect { bool isCondition() const { return m_category == CONDITION; } bool isAlarm() const { return m_specialClass == ALARM_CLS; } bool isMessage() const { return m_specialClass == MESSAGE_CLS; } + bool isAssetAdded() const { return m_specialClass == ASSET_ADDED_CLS; } bool isAssetChanged() const { return m_specialClass == ASSET_CHANGED_CLS; } bool isAssetRemoved() const { return m_specialClass == ASSET_REMOVED_CLS; } bool isTimeSeries() const { return m_representation == TIME_SERIES; } diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index e427c02b1..bb58cb0a1 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -142,6 +142,8 @@ namespace mtconnect { m_availability = dataItem; else if (dataItem->getType() == "ASSET_CHANGED") m_assetChanged = dataItem; + else if (dataItem->getType() == "ASSET_ADDED") + m_assetAdded = dataItem; else if (dataItem->getType() == "ASSET_REMOVED") m_assetRemoved = dataItem; else if (dataItem->getType() == "ASSET_COUNT") diff --git a/src/mtconnect/device_model/device.hpp b/src/mtconnect/device_model/device.hpp index d4ae2a80c..650fe3ecc 100644 --- a/src/mtconnect/device_model/device.hpp +++ b/src/mtconnect/device_model/device.hpp @@ -315,6 +315,7 @@ namespace mtconnect { DataItemPtr getAvailability() const { return m_availability; } DataItemPtr getAssetChanged() const { return m_assetChanged; } DataItemPtr getAssetRemoved() const { return m_assetRemoved; } + DataItemPtr getAssetAdded() const { return m_assetAdded; } DataItemPtr getAssetCount() const { return m_assetCount; } ///@} @@ -351,6 +352,7 @@ namespace mtconnect { DataItemPtr m_availability; DataItemPtr m_assetChanged; DataItemPtr m_assetRemoved; + DataItemPtr m_assetAdded; DataItemPtr m_assetCount; DataItemIndex m_dataItems; diff --git a/src/mtconnect/observation/observation.cpp b/src/mtconnect/observation/observation.cpp index fc8c56282..f2a3609c5 100644 --- a/src/mtconnect/observation/observation.cpp +++ b/src/mtconnect/observation/observation.cpp @@ -56,6 +56,7 @@ namespace mtconnect { factory->registerFactory("Events:Message", Message::getFactory()); factory->registerFactory("Events:MessageDiscrete", Message::getFactory()); + factory->registerFactory("Events:AssetAdded", AssetEvent::getFactory()); factory->registerFactory("Events:AssetChanged", AssetEvent::getFactory()); factory->registerFactory("Events:AssetRemoved", AssetEvent::getFactory()); factory->registerFactory("Events:DeviceAdded", DeviceEvent::getFactory()); diff --git a/src/mtconnect/pipeline/response_document.cpp b/src/mtconnect/pipeline/response_document.cpp index 42f4e0e08..bbfe56b7f 100644 --- a/src/mtconnect/pipeline/response_document.cpp +++ b/src/mtconnect/pipeline/response_document.cpp @@ -427,7 +427,7 @@ namespace mtconnect::pipeline { return true; } - if (di->isAssetChanged()) + if (di->isAssetChanged() || di->isAssetAdded()) out.m_assetEvents.emplace_back((obs)); else out.m_entities.emplace_back(obs); diff --git a/src/mtconnect/validation/observation_validations.hpp b/src/mtconnect/validation/observation_validations.hpp index 1c19ba4fa..f91d98024 100644 --- a/src/mtconnect/validation/observation_validations.hpp +++ b/src/mtconnect/validation/observation_validations.hpp @@ -4,6 +4,7 @@ Validation ControlledVocabularies { {{"ACTIVE", {SCHEMA_VERSION(1, 2), 0}}, {"INACTIVE", {SCHEMA_VERSION(1, 2), 0}}}}, {"Alarm", {}}, {"AssetChanged", {}}, + {"AssetAdded", {}}, {"AssetRemoved", {}}, {"Availability", {{"AVAILABLE", {SCHEMA_VERSION(1, 1), 0}}, {"UNAVAILABLE", {SCHEMA_VERSION(1, 1), 0}}}}, @@ -248,4 +249,4 @@ Validation ControlledVocabularies { {"ActivePowerSource", {}}, {"LocationNarrative", {}}, {"Thickness", {}}, - {"LocationSpatialGeographic", {}}}; \ No newline at end of file + {"LocationSpatialGeographic", {}}}; diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 4a3702dbd..05f82d637 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -284,6 +284,7 @@ add_agent_test(observation_validation TRUE pipeline) add_agent_test(correct_timestamp TRUE pipeline) add_agent_test(agent TRUE core) +add_agent_test(agent_asset TRUE core) add_agent_test(globals FALSE core) add_agent_test(config_parser FALSE configuration) diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 193545027..5966404e8 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -484,29 +484,34 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + auto max = seq - 1; { - query["at"] = to_string(seq); + auto s = to_string(seq); + query["at"] = s; sprintf(line, "'at' must be less than %d", int32_t(seq)); PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/current?at=252"); - ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Value", "252"); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", line); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", ("/current?at=" + s).c_str()); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter@name", "at"); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Value", s.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "251"); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", to_string(max).c_str()); } seq = circ.getFirstSequence() - 1; { query["at"] = to_string(seq); - sprintf(line, "'at' must be greater than %d", int32_t(seq)); + sprintf(line, "'at' must be greater than 0"); PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", line); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/current?at=0"); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter@name", "at"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Value", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "251"); + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", to_string(max).c_str()); } } @@ -2104,822 +2109,6 @@ TEST_F(AgentTest, should_handle_uuid_change) } } -// ------------------------- Asset Tests --------------------------------- - -TEST_F(AgentTest, should_store_assets_in_buffer) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - - auto rest = m_agentTestHelper->getRestService(); - ASSERT_TRUE(rest->getServer()->arePutsAllowed()); - string body = "TEST"; - QueryMap queries; - - queries["type"] = "Part"; - queries["device"] = "LinuxCNC"; - - ASSERT_EQ((unsigned int)4, agent->getAssetStorage()->getMaxAssets()); - ASSERT_EQ((unsigned int)0, agent->getAssetStorage()->getCount()); - - { - PARSE_XML_RESPONSE_PUT("/asset/123", body, queries); - ASSERT_EQ((unsigned int)1, agent->getAssetStorage()->getCount()); - } - - { - PARSE_XML_RESPONSE("/asset/123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetBufferSize", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST"); - } - - // The device should generate an asset changed event as well. - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged", "123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged@assetType", "Part"); - } -} - -TEST_F(AgentTest, should_handle_asset_buffer_and_buffer_limits) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - string body = "TEST 1"; - QueryMap queries; - - queries["device"] = "000"; - queries["type"] = "Part"; - - const auto &storage = agent->getAssetStorage(); - - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - ASSERT_EQ((unsigned int)0, storage->getCount()); - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); - } - - // Make sure replace works properly - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); - } - - body = "TEST 2"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)2, storage->getCount()); - ASSERT_EQ(2, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); - } - - body = "TEST 3"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)3, storage->getCount()); - ASSERT_EQ(3, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 3"); - } - - body = "TEST 4"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)4, storage->getCount()); - } - - { - PARSE_XML_RESPONSE("/asset/P4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 4"); - ASSERT_EQ(4, storage->getCountForType("Part")); - } - - // Test multiple asset get - { - PARSE_XML_RESPONSE("/assets"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[4]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[3]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 4"); - } - - // Test multiple asset get with filter - { - PARSE_XML_RESPONSE_QUERY("/asset", queries); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[4]", "TEST 4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[3]", "TEST 3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 1"); - } - - queries["count"] = "2"; - { - PARSE_XML_RESPONSE_QUERY("/assets", queries); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 2"); - } - - queries.erase("count"); - - body = "TEST 5"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P5"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 5"); - } - - { - PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "ASSET_NOT_FOUND"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: P1"); - } - - body = "TEST 6"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 6"); - } - - { - PARSE_XML_RESPONSE("/asset/P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); - } - - body = "TEST 7"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); - } - - body = "TEST 8"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)4, storage->getCount()); - ASSERT_EQ(4, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P6"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 8"); - } - - // Now since two and three have been modified, asset 4 should be removed. - { - PARSE_XML_RESPONSE("/asset/P4"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "ASSET_NOT_FOUND"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: P4"); - } -} - -TEST_F(AgentTest, should_report_asset_not_found_error) -{ - { - PARSE_XML_RESPONSE("/asset/123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "ASSET_NOT_FOUND"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: 123"); - } -} - -TEST_F(AgentTest, should_report_asset_not_found_2_6_error) -{ - m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, - {{configuration::Validation, false}}); - - { - PARSE_XML_RESPONSE("/asset/123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound@errorCode", "ASSET_NOT_FOUND"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound/m:ErrorMessage", "Cannot find asset: 123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound/m:AssetId", "123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound/m:URI", "/asset/123"); - } -} - -TEST_F(AgentTest, should_report_asset_not_found_2_6_error_with_multiple_assets) -{ - m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, - {{configuration::Validation, false}}); - - { - PARSE_XML_RESPONSE("/asset/123;456"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Errors/m:AssetNotFound[1]@errorCode", "ASSET_NOT_FOUND"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[1]/m:ErrorMessage", "Cannot find asset: 123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[1]/m:AssetId", "123"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[1]/m:URI", "/asset/123;456"); - - ASSERT_XML_PATH_EQUAL(doc, "//m:Errors/m:AssetNotFound[2]@errorCode", "ASSET_NOT_FOUND"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[2]/m:ErrorMessage", "Cannot find asset: 456"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[2]/m:AssetId", "456"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[2]/m:URI", "/asset/123;456"); - } -} - -TEST_F(AgentTest, should_handle_asset_from_adapter_on_one_line) -{ - addAdapter(); - auto agent = m_agentTestHelper->getAgent(); - const auto &storage = agent->getAssetStorage(); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - ASSERT_EQ((unsigned int)1, storage->getCount()); - - { - PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); - } -} - -TEST_F(AgentTest, should_handle_multiline_asset) -{ - addAdapter(); - auto agent = m_agentTestHelper->getAgent(); - const auto &storage = agent->getAssetStorage(); - - m_agentTestHelper->m_adapter->parseBuffer( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA\n"); - m_agentTestHelper->m_adapter->parseBuffer( - "\n" - " TEST 1\n" - " Some Text\n" - " XXX\n"); - m_agentTestHelper->m_adapter->parseBuffer( - "\n" - "--multiline--AAAA\n"); - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - ASSERT_EQ((unsigned int)1, storage->getCount()); - - { - PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:PartXXX", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:Extra", "XXX"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@deviceUuid", "000"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2021-02-01T12:00:00Z"); - } - - // Make sure we can still add a line and we are out of multiline mode... - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", "204"); - } -} - -TEST_F(AgentTest, should_handle_bad_asset_from_adapter) -{ - addAdapter(); - const auto &storage = m_agentTestHelper->m_agent->getAssetStorage(); - - m_agentTestHelper->m_adapter->parseBuffer( - "2021-02-01T12:00:00Z|@ASSET@|111|CuttingTool|--multiline--AAAA\n"); - m_agentTestHelper->m_adapter->parseBuffer((getFile("asset4.xml") + "\n").c_str()); - m_agentTestHelper->m_adapter->parseBuffer("--multiline--AAAA\n"); - ASSERT_EQ((unsigned int)0, storage->getCount()); -} - -TEST_F(AgentTest, should_handle_asset_removal_from_REST_api) -{ - string body = "TEST 1"; - QueryMap query; - - query["device"] = "LinuxCNC"; - query["type"] = "Part"; - - const auto &storage = m_agentTestHelper->m_agent->getAssetStorage(); - - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - ASSERT_EQ((unsigned int)0, storage->getCount()); - - { - PARSE_XML_RESPONSE_PUT("/asset", body, query); - ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); - } - - // Make sure replace works properly - { - PARSE_XML_RESPONSE_PUT("/asset", body, query); - ASSERT_EQ((unsigned int)1, storage->getCount()); - ASSERT_EQ(1, storage->getCountForType("Part")); - } - - body = "TEST 2"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, query); - ASSERT_EQ((unsigned int)2, storage->getCount()); - ASSERT_EQ(2, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); - } - - body = "TEST 3"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, query); - ASSERT_EQ((unsigned int)3, storage->getCount()); - ASSERT_EQ(3, storage->getCountForType("Part")); - } - - { - PARSE_XML_RESPONSE("/asset/P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 3"); - } - - body = "TEST 2"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, query); - ASSERT_EQ((unsigned int)3, storage->getCount(false)); - ASSERT_EQ(3, storage->getCountForType("Part", false)); - } - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); - } - - { - PARSE_XML_RESPONSE("/asset"); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); - } - - query["removed"] = "true"; - { - PARSE_XML_RESPONSE_QUERY("/asset", query); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 3); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]@removed", "true"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[3]", "TEST 3"); - } -} - -TEST_F(AgentTest, should_handle_asset_removal_from_adapter) -{ - addAdapter(); - QueryMap query; - auto agent = m_agentTestHelper->getAgent(); - const auto &storage = agent->getAssetStorage(); - - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); - ASSERT_EQ((unsigned int)1, storage->getCount()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 2"); - ASSERT_EQ((unsigned int)2, storage->getCount()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P3|Part|TEST 3"); - ASSERT_EQ((unsigned int)3, storage->getCount()); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); - } - - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P2\r"); - ASSERT_EQ((unsigned int)3, storage->getCount(false)); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); - } - - { - PARSE_XML_RESPONSE("/asset"); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); - } - - // TODO: When asset is removed and the content is literal, it will - // not regenerate the attributes for the asset. - query["removed"] = "true"; - { - PARSE_XML_RESPONSE_QUERY("/asset", query); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 3); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[3]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); - } -} - -TEST_F(AgentTest, AssetAdditionOfAssetChanged12) -{ - m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "1.2", 25); - - { - PARSE_XML_RESPONSE("/LinuxCNC/probe"); - ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); - ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", nullptr); - ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 0); - } -} - -TEST_F(AgentTest, AssetAdditionOfAssetRemoved13) -{ - m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "1.3", 25); - - { - PARSE_XML_RESPONSE("/LinuxCNC/probe"); - ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); - ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", nullptr); - ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 1); - } -} - -TEST_F(AgentTest, AssetAdditionOfAssetRemoved15) -{ - m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "1.5", 25); - { - PARSE_XML_RESPONSE("/LinuxCNC/probe"); - ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); - ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", "true"); - ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 1); - } -} - -TEST_F(AgentTest, AssetPrependId) -{ - addAdapter(); - auto agent = m_agentTestHelper->getAgent(); - const auto &storage = agent->getAssetStorage(); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|@1|Part|TEST 1"); - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - ASSERT_EQ((unsigned int)1, storage->getCount()); - - { - PARSE_XML_RESPONSE("/asset/0001"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "0001"); - } -} - -TEST_F(AgentTest, RemoveLastAssetChanged) -{ - addAdapter(); - auto agent = m_agentTestHelper->getAgent(); - const auto &storage = agent->getAssetStorage(); - - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); - ASSERT_EQ((unsigned int)1, storage->getCount()); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); - } - - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); - ASSERT_EQ((unsigned int)1, storage->getCount(false)); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); - } -} - -TEST_F(AgentTest, RemoveAssetUsingHttpDelete) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - addAdapter(); - const auto &storage = agent->getAssetStorage(); - - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); - ASSERT_EQ((unsigned int)1, storage->getCount(false)); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); - } - - { - PARSE_XML_RESPONSE_DELETE("/asset/P1"); - } - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); - } -} - -TEST_F(AgentTest, AssetChangedWhenUnavailable) -{ - addAdapter(); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "UNAVAILABLE"); - } -} - -TEST_F(AgentTest, RemoveAllAssets) -{ - addAdapter(); - auto agent = m_agentTestHelper->getAgent(); - const auto &storage = agent->getAssetStorage(); - - ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); - ASSERT_EQ((unsigned int)1, storage->getCount()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 2"); - ASSERT_EQ((unsigned int)2, storage->getCount()); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P3|Part|TEST 3"); - ASSERT_EQ((unsigned int)3, storage->getCount()); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); - } - - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ALL_ASSETS@|Part"); - ASSERT_EQ((unsigned int)3, storage->getCount(false)); - - { - PARSE_XML_RESPONSE("/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P3"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); - } - - ASSERT_EQ((unsigned int)0, storage->getCount()); - - { - PARSE_XML_RESPONSE("/assets"); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 0); - } - - // TODO: When asset is removed and the content is literal, it will - // not regenerate the attributes for the asset. - { - QueryMap q {{"removed", "true"}}; - PARSE_XML_RESPONSE_QUERY("/asset", q); - ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 3); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "0"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[3]", "TEST 1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); - } -} - -TEST_F(AgentTest, AssetProbe) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - string body = "TEST 1"; - QueryMap queries; - const auto &storage = agent->getAssetStorage(); - - queries["device"] = "LinuxCNC"; - queries["type"] = "Part"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)1, storage->getCount()); - } - { - PARSE_XML_RESPONSE_PUT("/asset/P2", body, queries); - ASSERT_EQ((unsigned int)2, storage->getCount()); - } - - { - PARSE_XML_RESPONSE("/probe"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount@assetType", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount", "2"); - } -} - -TEST_F(AgentTest, ResponseToHTTPAssetPutErrors) -{ - m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - - const string body { - R"DOC( - - - NEW - - - - - - - -)DOC"}; - - QueryMap queries; - - queries["device"] = "LinuxCNC"; - queries["type"] = "CuttingTool"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[1]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[1]", - "Asset parsed with errors."); - - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[2]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[2]", - "FunctionalLength(VALUE): Property VALUE is required and not provided"); - - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[3]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[3]", - "Measurements: Invalid element 'FunctionalLength'"); - - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[4]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[4]", - "CuttingDiameterMax(VALUE): Property VALUE is required and not provided"); - - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[5]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[5]", - "Measurements: Invalid element 'CuttingDiameterMax'"); - - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[6]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[6]", - "Measurements(Measurement): Entity list requirement Measurement must " - "have at least 1 entries, 0 found"); - - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[7]@errorCode", - "INVALID_REQUEST"); - ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[7]", - "CuttingToolLifeCycle: Invalid element 'Measurements'"); - } -} - -TEST_F(AgentTest, update_asset_count_data_item_v2_0) -{ - m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 10, "2.0", 4, true); - addAdapter(); - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); - } - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 1"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "Part"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); - } - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|T1|Tool|TEST 1"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "1"); - } - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|T2|Tool|TEST 1"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "2"); - } - - m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|@ASSET@|T3|Tool|TEST 1"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); - } - - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); - } - - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P2"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_COUNT(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", 0); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); - } - - m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ALL_ASSETS@|"); - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_COUNT(doc, "//m:AssetCountDataSet/*", 0); - } -} /// @name Streaming Tests /// Tests that validate HTTP long poll behavior of the agent @@ -3222,108 +2411,6 @@ TEST_F(AgentTest, shound_add_asset_count_when_20) } } -TEST_F(AgentTest, asset_count_should_not_occur_in_header_post_20) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - - string body = "TEST 1"; - QueryMap queries; - const auto &storage = agent->getAssetStorage(); - - queries["device"] = "LinuxCNC"; - queries["type"] = "Part"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body, queries); - ASSERT_EQ((unsigned int)1, storage->getCount()); - } - { - PARSE_XML_RESPONSE_PUT("/asset/P2", body, queries); - ASSERT_EQ((unsigned int)2, storage->getCount()); - } - - { - PARSE_XML_RESPONSE("/probe"); - ASSERT_XML_PATH_COUNT(doc, "//m:Header/*", 0); - } -} - -TEST_F(AgentTest, asset_count_should_track_asset_additions_by_type) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - - string body1 = "TEST 1"; - QueryMap queries; - const auto &storage = agent->getAssetStorage(); - - queries["device"] = "LinuxCNC"; - queries["type"] = "Part"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body1, queries); - ASSERT_EQ(1u, storage->getCount()); - } - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); - } - - string body2 = "TEST 2"; - queries["type"] = "PartThing"; - - { - PARSE_XML_RESPONSE_PUT("/asset", body2, queries); - ASSERT_EQ(2u, storage->getCount()); - } - - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "1"); - } - - { - PARSE_XML_RESPONSE_PUT("/asset/P3", body2, queries); - ASSERT_EQ(3u, storage->getCount()); - } - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "2"); - } - - body2 = "TEST 2"; - - { - PARSE_XML_RESPONSE_PUT("/asset/P3", body2, queries); - ASSERT_EQ(2u, storage->getCount()); - } - { - PARSE_XML_RESPONSE("/LinuxCNC/current"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); - ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "1"); - } -} - -TEST_F(AgentTest, asset_should_also_work_using_post_with_assets) -{ - auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - - string body = "TEST 1"; - QueryMap queries; - const auto &storage = agent->getAssetStorage(); - - { - PARSE_XML_RESPONSE_PUT("/assets", body, queries); - ASSERT_EQ((unsigned int)1, storage->getCount()); - } - { - PARSE_XML_RESPONSE_PUT("/assets/P2", body, queries); - ASSERT_EQ((unsigned int)2, storage->getCount()); - } -} - TEST_F(AgentTest, pre_start_hook_should_be_called) { bool called = false; diff --git a/test_package/test_utilities.hpp b/test_package/test_utilities.hpp index 96491b907..921f2cfd4 100644 --- a/test_package/test_utilities.hpp +++ b/test_package/test_utilities.hpp @@ -111,6 +111,9 @@ inline void fillAttribute(std::string &xmlString, const std::string &attribute, #define ASSERT_XML_PATH_COUNT(doc, path, expected) \ xpathTestCount(doc, path, expected, __FILE__, __LINE__) +#define DUMP_XML(doc) \ + dumpXml(doc) + inline void failIf(bool condition, const std::string &message, const std::string &file, int line) { ASSERT_FALSE(condition) << file << "(" << line << "): Failed " << message; @@ -131,6 +134,18 @@ inline void assertIf(bool condition, const std::string &message, const std::stri using ValueResponse = std::pair, std::optional>; +inline std::string dumpXml(xmlDocPtr doc) +{ + int size; + xmlChar *memory; + xmlDocDumpFormatMemory(doc, &memory, &size, 1); + + std::string s((const char *) memory, size); + xmlFree(memory); + + return s; +} + inline ValueResponse xpathValue(xmlDocPtr doc, const char *xpath, const std::string &file, int line, bool noValue = false) { @@ -172,15 +187,10 @@ inline ValueResponse xpathValue(xmlDocPtr doc, const char *xpath, const std::str if (!obj || !obj->nodesetval || obj->nodesetval->nodeNr == 0) { - int size; - xmlChar *memory; - xmlDocDumpFormatMemory(doc, &memory, &size, 1); - stringstream message; message << file << "(" << line << "): " << "Xpath " << xpath << " did not match any nodes in XML document" << endl - << ((const char *)memory); - xmlFree(memory); + << dumpXml(doc); if (obj) xmlXPathFreeObject(obj); From c397f5c024b978e1f72a359182935ae4137529dd Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Tue, 26 Aug 2025 19:11:23 +0200 Subject: [PATCH 2/2] Added agent asset test --- src/mtconnect/agent.cpp | 21 +- src/mtconnect/agent.hpp | 4 +- test_package/agent_asset_test.cpp | 1138 +++++++++++++++++++++++++++++ 3 files changed, 1156 insertions(+), 7 deletions(-) create mode 100644 test_package/agent_asset_test.cpp diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 051f8e5a7..5263e336d 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -366,12 +366,24 @@ namespace mtconnect { if (device) { DataItemPtr di; + auto added = device->getAssetAdded(); if (asset->isRemoved()) di = device->getAssetRemoved(); - else if (old || !device->getAssetAdded()) + else if (old || !added) + { di = device->getAssetChanged(); - else if (device->getAssetAdded()) - di = device->getAssetAdded(); + /// If we have changed the asset that is currently recorded as added. Make added unavailable. + if (added) + { + auto last = getLatest(added); + if (last && asset->getAssetId() == last->getValue()) + { + m_loopback->receive(added, {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); + } + } + } + else if (added) + di = added; if (di) { entity::Properties props {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}; @@ -783,11 +795,10 @@ namespace mtconnect { m_loopback->receive(added, {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); } } - } } } - + // --------------------------------------- // Agent Device // --------------------------------------- diff --git a/src/mtconnect/agent.hpp b/src/mtconnect/agent.hpp index c0c1a0821..8c08c915a 100644 --- a/src/mtconnect/agent.hpp +++ b/src/mtconnect/agent.hpp @@ -378,9 +378,9 @@ namespace mtconnect { bool removeAllAssets(const std::optional device, const std::optional type, const std::optional time, asset::AssetList &list); - /// @brief Send asset removed observation when an asset is removed. + /// @brief Send asset changed and added observation when an asset is removed. /// - /// Also sets asset changed to `UNAVAILABLE` if the asset removed asset was the last changed. + /// Also sets asset changed and added to `UNAVAILABLE` if the asset removed asset was the last changed. /// /// @param device The device related to the asset /// @param asset The asset diff --git a/test_package/agent_asset_test.cpp b/test_package/agent_asset_test.cpp new file mode 100644 index 000000000..07bf35843 --- /dev/null +++ b/test_package/agent_asset_test.cpp @@ -0,0 +1,1138 @@ +// +// Copyright Copyright 2009-2024, AMT – The Association For Manufacturing Technology (“AMT”) +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/// @file +/// Integration tests for the agent. Covers many behaviours of the agent across many modules. + +// Ensure that gtest is the first header otherwise Windows raises an error +#include +// Keep this comment to keep gtest.h above. (clang-format off/on is not working here!) + +#include +#include +#include +#include +#include +#include + +#include "agent_test_helper.hpp" +#include "mtconnect/agent.hpp" +#include "mtconnect/asset/file_asset.hpp" +#include "mtconnect/device_model/reference.hpp" +#include "mtconnect/printer//xml_printer.hpp" +#include "mtconnect/source/adapter/adapter.hpp" +#include "test_utilities.hpp" + +#if defined(WIN32) && _MSC_VER < 1500 +typedef __int64 int64_t; +#endif + +// Registers the fixture into the 'registry' +using namespace std; +using namespace std::chrono; +using namespace mtconnect; +using namespace mtconnect::sink::rest_sink; +using namespace mtconnect::source::adapter; +using namespace mtconnect::observation; + +using status = boost::beast::http::status; + +// main +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + +class AgentAssetTest : public testing::Test +{ +public: + typedef std::map map_type; + using queue_type = list; + +protected: + void SetUp() override + { + m_agentTestHelper = make_unique(); + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 25, true); + m_agentId = to_string(getCurrentTimeInSec()); + } + + void TearDown() override { m_agentTestHelper.reset(); } + + void addAdapter(ConfigOptions options = ConfigOptions {}) + { + m_agentTestHelper->addAdapter(options, "localhost", 7878, + m_agentTestHelper->m_agent->getDefaultDevice()->getName()); + } + +public: + std::string m_agentId; + std::unique_ptr m_agentTestHelper; + + std::chrono::milliseconds m_delay {}; +}; + +// ------------------------- Asset Tests --------------------------------- + +TEST_F(AgentAssetTest, should_store_assets_in_buffer) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); + + auto rest = m_agentTestHelper->getRestService(); + ASSERT_TRUE(rest->getServer()->arePutsAllowed()); + string body = "TEST"; + QueryMap queries; + + queries["type"] = "Part"; + queries["device"] = "LinuxCNC"; + + ASSERT_EQ((unsigned int)4, agent->getAssetStorage()->getMaxAssets()); + ASSERT_EQ((unsigned int)0, agent->getAssetStorage()->getCount()); + + { + PARSE_XML_RESPONSE_PUT("/asset/123", body, queries); + ASSERT_EQ((unsigned int)1, agent->getAssetStorage()->getCount()); + } + + { + PARSE_XML_RESPONSE("/asset/123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetBufferSize", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST"); + } + + // The device should generate an asset changed event as well. + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged", "123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetChanged@assetType", "Part"); + } +} + +TEST_F(AgentAssetTest, should_store_assets_in_buffer_and_generate_asset_added) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, true); + + auto rest = m_agentTestHelper->getRestService(); + ASSERT_TRUE(rest->getServer()->arePutsAllowed()); + string body = "TEST"; + QueryMap queries; + + queries["type"] = "Part"; + queries["device"] = "LinuxCNC"; + + ASSERT_EQ((unsigned int)4, agent->getAssetStorage()->getMaxAssets()); + ASSERT_EQ((unsigned int)0, agent->getAssetStorage()->getCount()); + + { + PARSE_XML_RESPONSE_PUT("/asset/123", body, queries); + ASSERT_EQ((unsigned int)1, agent->getAssetStorage()->getCount()); + } + + { + PARSE_XML_RESPONSE("/asset/123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetBufferSize", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST"); + } + + // The device should generate an asset added event as well. + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetAdded", "123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:AssetAdded@assetType", "Part"); + } +} + + +TEST_F(AgentAssetTest, should_handle_asset_buffer_and_buffer_limits) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); + string body = "TEST 1"; + QueryMap queries; + + queries["device"] = "000"; + queries["type"] = "Part"; + + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + ASSERT_EQ((unsigned int)0, storage->getCount()); + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)1, storage->getCount()); + ASSERT_EQ(1, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + } + + // Make sure replace works properly + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)1, storage->getCount()); + ASSERT_EQ(1, storage->getCountForType("Part")); + } + + body = "TEST 2"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)2, storage->getCount()); + ASSERT_EQ(2, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); + } + + body = "TEST 3"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)3, storage->getCount()); + ASSERT_EQ(3, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 3"); + } + + body = "TEST 4"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)4, storage->getCount()); + } + + { + PARSE_XML_RESPONSE("/asset/P4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 4"); + ASSERT_EQ(4, storage->getCountForType("Part")); + } + + // Test multiple asset get + { + PARSE_XML_RESPONSE("/assets"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[4]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[3]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 4"); + } + + // Test multiple asset get with filter + { + PARSE_XML_RESPONSE_QUERY("/asset", queries); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[4]", "TEST 4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[3]", "TEST 3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 1"); + } + + queries["count"] = "2"; + { + PARSE_XML_RESPONSE_QUERY("/assets", queries); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[1]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part[2]", "TEST 2"); + } + + queries.erase("count"); + + body = "TEST 5"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)4, storage->getCount()); + ASSERT_EQ(4, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P5"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 5"); + } + + { + PARSE_XML_RESPONSE("/asset/P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "ASSET_NOT_FOUND"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: P1"); + } + + body = "TEST 6"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)4, storage->getCount()); + ASSERT_EQ(4, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 6"); + } + + { + PARSE_XML_RESPONSE("/asset/P2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); + } + + body = "TEST 7"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)4, storage->getCount()); + ASSERT_EQ(4, storage->getCountForType("Part")); + } + + body = "TEST 8"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)4, storage->getCount()); + ASSERT_EQ(4, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P6"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 8"); + } + + // Now since two and three have been modified, asset 4 should be removed. + { + PARSE_XML_RESPONSE("/asset/P4"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "ASSET_NOT_FOUND"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: P4"); + } +} + +TEST_F(AgentAssetTest, should_report_asset_not_found_error) +{ + { + PARSE_XML_RESPONSE("/asset/123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "ASSET_NOT_FOUND"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error", "Cannot find asset: 123"); + } +} + +TEST_F(AgentAssetTest, should_report_asset_not_found_2_6_error) +{ + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, + {{configuration::Validation, false}}); + + { + PARSE_XML_RESPONSE("/asset/123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound@errorCode", "ASSET_NOT_FOUND"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound/m:ErrorMessage", "Cannot find asset: 123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound/m:AssetId", "123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound/m:URI", "/asset/123"); + } +} + +TEST_F(AgentAssetTest, should_report_asset_not_found_2_6_error_with_multiple_assets) +{ + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, + {{configuration::Validation, false}}); + + { + PARSE_XML_RESPONSE("/asset/123;456"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Errors/m:AssetNotFound[1]@errorCode", "ASSET_NOT_FOUND"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[1]/m:ErrorMessage", "Cannot find asset: 123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[1]/m:AssetId", "123"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[1]/m:URI", "/asset/123;456"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:Errors/m:AssetNotFound[2]@errorCode", "ASSET_NOT_FOUND"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[2]/m:ErrorMessage", "Cannot find asset: 456"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[2]/m:AssetId", "456"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetNotFound[2]/m:URI", "/asset/123;456"); + } +} + +TEST_F(AgentAssetTest, should_handle_asset_from_adapter_on_one_line) +{ + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/asset/P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + } +} + +TEST_F(AgentAssetTest, should_handle_multiline_asset) +{ + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + m_agentTestHelper->m_adapter->parseBuffer( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|--multiline--AAAA\n"); + m_agentTestHelper->m_adapter->parseBuffer( + "\n" + " TEST 1\n" + " Some Text\n" + " XXX\n"); + m_agentTestHelper->m_adapter->parseBuffer( + "\n" + "--multiline--AAAA\n"); + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/asset/P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:PartXXX", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part/m:Extra", "XXX"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part@deviceUuid", "000"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part@timestamp", "2021-02-01T12:00:00Z"); + } + + // Make sure we can still add a line and we are out of multiline mode... + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", "204"); + } +} + +TEST_F(AgentAssetTest, should_handle_bad_asset_from_adapter) +{ + addAdapter(); + const auto &storage = m_agentTestHelper->m_agent->getAssetStorage(); + + m_agentTestHelper->m_adapter->parseBuffer( + "2021-02-01T12:00:00Z|@ASSET@|111|CuttingTool|--multiline--AAAA\n"); + m_agentTestHelper->m_adapter->parseBuffer((getFile("asset4.xml") + "\n").c_str()); + m_agentTestHelper->m_adapter->parseBuffer("--multiline--AAAA\n"); + ASSERT_EQ((unsigned int)0, storage->getCount()); +} + +TEST_F(AgentAssetTest, should_handle_asset_removal_from_REST_api) +{ + string body = "TEST 1"; + QueryMap query; + + query["device"] = "LinuxCNC"; + query["type"] = "Part"; + + const auto &storage = m_agentTestHelper->m_agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + ASSERT_EQ((unsigned int)0, storage->getCount()); + + { + PARSE_XML_RESPONSE_PUT("/asset", body, query); + ASSERT_EQ((unsigned int)1, storage->getCount()); + ASSERT_EQ(1, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + } + + // Make sure replace works properly + { + PARSE_XML_RESPONSE_PUT("/asset", body, query); + ASSERT_EQ((unsigned int)1, storage->getCount()); + ASSERT_EQ(1, storage->getCountForType("Part")); + } + + body = "TEST 2"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, query); + ASSERT_EQ((unsigned int)2, storage->getCount()); + ASSERT_EQ(2, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 2"); + } + + body = "TEST 3"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, query); + ASSERT_EQ((unsigned int)3, storage->getCount()); + ASSERT_EQ(3, storage->getCountForType("Part")); + } + + { + PARSE_XML_RESPONSE("/asset/P3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 3"); + } + + body = "TEST 2"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, query); + ASSERT_EQ((unsigned int)3, storage->getCount(false)); + ASSERT_EQ(3, storage->getCountForType("Part", false)); + } + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + } + + { + PARSE_XML_RESPONSE("/asset"); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); + } + + query["removed"] = "true"; + { + PARSE_XML_RESPONSE_QUERY("/asset", query); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 3); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]@removed", "true"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[3]", "TEST 3"); + } +} + +TEST_F(AgentAssetTest, should_handle_asset_removal_from_adapter) +{ + addAdapter(); + QueryMap query; + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 2"); + ASSERT_EQ((unsigned int)2, storage->getCount()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P3|Part|TEST 3"); + ASSERT_EQ((unsigned int)3, storage->getCount()); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P2\r"); + ASSERT_EQ((unsigned int)3, storage->getCount(false)); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + } + + { + PARSE_XML_RESPONSE("/asset"); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 2); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); + } + + // TODO: When asset is removed and the content is literal, it will + // not regenerate the attributes for the asset. + query["removed"] = "true"; + { + PARSE_XML_RESPONSE_QUERY("/asset", query); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 3); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[3]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); + } +} + +TEST_F(AgentAssetTest, should_add_asset_changed_without_discrete_in_1_3) +{ + m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "1.2", 25); + + { + PARSE_XML_RESPONSE("/LinuxCNC/probe"); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); + ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", nullptr); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 0); + } +} + +TEST_F(AgentAssetTest, should_add_asset_removed_in_1_3) +{ + m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "1.3", 25); + + { + PARSE_XML_RESPONSE("/LinuxCNC/probe"); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); + ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", nullptr); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 1); + } +} + +TEST_F(AgentAssetTest, should_add_asset_changed_with_discrete_in_1_5) +{ + m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "1.5", 25); + { + PARSE_XML_RESPONSE("/LinuxCNC/probe"); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); + ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", "true"); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 1); + } +} + +TEST_F(AgentAssetTest, should_add_asset_changed_and_asset_added_with_discrete_in_2_6) +{ + m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.6", 25); + { + PARSE_XML_RESPONSE("/LinuxCNC/probe"); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_ADDED']", 1); + ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_CHANGED']@discrete", "true"); + ASSERT_XML_PATH_EQUAL(doc, "//m:DataItem[@type='ASSET_ADDED']@discrete", "true"); + ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_REMOVED']", 1); + } +} + +TEST_F(AgentAssetTest, AssetPrependId) +{ + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|@1|Part|TEST 1"); + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/asset/0001"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Part@assetId", "0001"); + } +} + +TEST_F(AgentAssetTest, should_remove_changed_asset) +{ + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); + ASSERT_EQ((unsigned int)1, storage->getCount(false)); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + } +} + +TEST_F(AgentAssetTest, should_remove_changed_observation_asset_in_2_6) +{ + m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.6", 25); + + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); + } + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 2"); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + } +} + +TEST_F(AgentAssetTest, should_remove_added_asset_observation_in_2_6) +{ + m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.6", 25); + + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); + ASSERT_EQ((unsigned int)1, storage->getCount(false)); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded@assetType", "Part"); + } +} + + + +TEST_F(AgentAssetTest, should_remove_asset_using_http_delete) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); + addAdapter(); + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)1, storage->getCount(false)); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + } + + { + PARSE_XML_RESPONSE_DELETE("/asset/P1"); + } + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + } +} + +TEST_F(AgentAssetTest, asset_changed_and_removed_should_be_defaulted_to_unavailable) +{ + addAdapter(); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "UNAVAILABLE"); + } +} + +TEST_F(AgentAssetTest, in_2_6_asset_changed_removed_and_added_should_be_defaulted_to_unavailable) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, true); + addAdapter(); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetAdded", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "UNAVAILABLE"); + } +} + +TEST_F(AgentAssetTest, should_remove_all_assets) +{ + addAdapter(); + auto agent = m_agentTestHelper->getAgent(); + const auto &storage = agent->getAssetStorage(); + + ASSERT_EQ((unsigned int)4, storage->getMaxAssets()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + ASSERT_EQ((unsigned int)1, storage->getCount()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 2"); + ASSERT_EQ((unsigned int)2, storage->getCount()); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P3|Part|TEST 3"); + ASSERT_EQ((unsigned int)3, storage->getCount()); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "P3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ALL_ASSETS@|Part"); + ASSERT_EQ((unsigned int)3, storage->getCount(false)); + + { + PARSE_XML_RESPONSE("/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved", "P3"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetRemoved@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged", "UNAVAILABLE"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetChanged@assetType", "Part"); + } + + ASSERT_EQ((unsigned int)0, storage->getCount()); + + { + PARSE_XML_RESPONSE("/assets"); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 0); + } + + // TODO: When asset is removed and the content is literal, it will + // not regenerate the attributes for the asset. + { + QueryMap q {{"removed", "true"}}; + PARSE_XML_RESPONSE_QUERY("/asset", q); + ASSERT_XML_PATH_COUNT(doc, "//m:Assets/*", 3); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header@assetCount", "0"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[3]", "TEST 1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[2]", "TEST 2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Assets/*[1]", "TEST 3"); + } +} + +TEST_F(AgentAssetTest, probe_should_have_the_asset_counts) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); + string body = "TEST 1"; + QueryMap queries; + const auto &storage = agent->getAssetStorage(); + + queries["device"] = "LinuxCNC"; + queries["type"] = "Part"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)1, storage->getCount()); + } + { + PARSE_XML_RESPONSE_PUT("/asset/P2", body, queries); + ASSERT_EQ((unsigned int)2, storage->getCount()); + } + + { + PARSE_XML_RESPONSE("/probe"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount@assetType", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:Header/m:AssetCounts/m:AssetCount", "2"); + } +} + +TEST_F(AgentAssetTest, should_respond_to_http_push_with_list_of_errors) +{ + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); + + const string body { + R"DOC( + + + NEW + + + + + + + +)DOC"}; + + QueryMap queries; + + queries["device"] = "LinuxCNC"; + queries["type"] = "CuttingTool"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[1]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[1]", + "Asset parsed with errors."); + + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[2]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[2]", + "FunctionalLength(VALUE): Property VALUE is required and not provided"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[3]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[3]", + "Measurements: Invalid element 'FunctionalLength'"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[4]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[4]", + "CuttingDiameterMax(VALUE): Property VALUE is required and not provided"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[5]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[5]", + "Measurements: Invalid element 'CuttingDiameterMax'"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[6]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[6]", + "Measurements(Measurement): Entity list requirement Measurement must " + "have at least 1 entries, 0 found"); + + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[7]@errorCode", + "INVALID_REQUEST"); + ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error[7]", + "CuttingToolLifeCycle: Invalid element 'Measurements'"); + } +} + +TEST_F(AgentAssetTest, update_asset_count_data_item_v2_0) +{ + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 10, "2.0", 4, true); + addAdapter(); + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P1|Part|TEST 1"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + } + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|P2|Part|TEST 1"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry@key", "Part"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + } + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|T1|Tool|TEST 1"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "1"); + } + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|T2|Tool|TEST 1"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "2"); + } + + m_agentTestHelper->m_adapter->processData( + "2021-02-01T12:00:00Z|@ASSET@|T3|Tool|TEST 1"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "2"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P1"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ASSET@|P2"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_COUNT(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", 0); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Tool']", "3"); + } + + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|@REMOVE_ALL_ASSETS@|"); + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_COUNT(doc, "//m:AssetCountDataSet/*", 0); + } +} + +TEST_F(AgentAssetTest, asset_count_should_not_occur_in_header_post_20) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); + + string body = "TEST 1"; + QueryMap queries; + const auto &storage = agent->getAssetStorage(); + + queries["device"] = "LinuxCNC"; + queries["type"] = "Part"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body, queries); + ASSERT_EQ((unsigned int)1, storage->getCount()); + } + { + PARSE_XML_RESPONSE_PUT("/asset/P2", body, queries); + ASSERT_EQ((unsigned int)2, storage->getCount()); + } + + { + PARSE_XML_RESPONSE("/probe"); + ASSERT_XML_PATH_COUNT(doc, "//m:Header/*", 0); + } +} + +TEST_F(AgentAssetTest, asset_count_should_track_asset_additions_by_type) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); + + string body1 = "TEST 1"; + QueryMap queries; + const auto &storage = agent->getAssetStorage(); + + queries["device"] = "LinuxCNC"; + queries["type"] = "Part"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body1, queries); + ASSERT_EQ(1u, storage->getCount()); + } + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + } + + string body2 = "TEST 2"; + queries["type"] = "PartThing"; + + { + PARSE_XML_RESPONSE_PUT("/asset", body2, queries); + ASSERT_EQ(2u, storage->getCount()); + } + + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "1"); + } + + { + PARSE_XML_RESPONSE_PUT("/asset/P3", body2, queries); + ASSERT_EQ(3u, storage->getCount()); + } + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "2"); + } + + body2 = "TEST 2"; + + { + PARSE_XML_RESPONSE_PUT("/asset/P3", body2, queries); + ASSERT_EQ(2u, storage->getCount()); + } + { + PARSE_XML_RESPONSE("/LinuxCNC/current"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='Part']", "1"); + ASSERT_XML_PATH_EQUAL(doc, "//m:AssetCountDataSet/m:Entry[@key='PartThing']", "1"); + } +} + +TEST_F(AgentAssetTest, asset_should_also_work_using_post_with_assets) +{ + auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); + + string body = "TEST 1"; + QueryMap queries; + const auto &storage = agent->getAssetStorage(); + + { + PARSE_XML_RESPONSE_PUT("/assets", body, queries); + ASSERT_EQ((unsigned int)1, storage->getCount()); + } + { + PARSE_XML_RESPONSE_PUT("/assets/P2", body, queries); + ASSERT_EQ((unsigned int)2, storage->getCount()); + } +} +