This is an automated email from the ASF dual-hosted git repository.

szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 4a3c96967 MINIFICPP-2920 - PutOPCProcessor creates writable node 
(#2279)
4a3c96967 is described below

commit 4a3c969677b46d2206b884810926e70e3dacf0b9
Author: Adam Debreceni <[email protected]>
AuthorDate: Fri Oct 9 16:33:55 2026 +0200

    MINIFICPP-2920 - PutOPCProcessor creates writable node (#2279)
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 extensions/opc/src/OPCCommon.cpp              |  2 ++
 extensions/opc/tests/PutOPCProcessorTests.cpp | 31 ++++++++++++++++++++++++
 extensions/opc/tests/features/opcua.feature   | 34 +++++++++++++++++----------
 3 files changed, 55 insertions(+), 12 deletions(-)

diff --git a/extensions/opc/src/OPCCommon.cpp b/extensions/opc/src/OPCCommon.cpp
index 8bf836a4a..6c1b8b568 100644
--- a/extensions/opc/src/OPCCommon.cpp
+++ b/extensions/opc/src/OPCCommon.cpp
@@ -430,6 +430,8 @@ UA_StatusCode 
Client::translateBrowsePathsToNodeIdsRequest(const std::string& pa
 template<typename T>
 UA_StatusCode Client::add_node(const UA_NodeId parent_node_id, const UA_NodeId 
target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name, T 
value, UA_NodeId *received_node_id) {
   UA_VariableAttributes attr = UA_VariableAttributes_default;
+  attr.accessLevel |= UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+  attr.userAccessLevel |= UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
   add_value_to_variant(&attr.value, value);
   char local[6] = "en-US";  // NOLINT(cppcoreguidelines-avoid-c-arrays)
   attr.displayName = UA_LOCALIZEDTEXT(local, 
const_cast<char*>(browse_name.data()));
diff --git a/extensions/opc/tests/PutOPCProcessorTests.cpp 
b/extensions/opc/tests/PutOPCProcessorTests.cpp
index 7197d05c7..c53d672c2 100644
--- a/extensions/opc/tests/PutOPCProcessorTests.cpp
+++ b/extensions/opc/tests/PutOPCProcessorTests.cpp
@@ -105,6 +105,37 @@ TEST_CASE("Test creating a new node with path node id", 
"[putopcprocessor]") {
   verifyCreatedNode(expected_node, controller);
 }
 
+TEST_CASE("Test updating a node previously created by the processor", 
"[putopcprocessor]") {
+  OpcUaTestServer server;
+  server.start();
+  SingleProcessorTestController 
controller{minifi::test::utils::make_processor<processors::PutOPCProcessor>("PutOPCProcessor")};
+  auto put_opc_processor = controller.getProcessor();
+
+  NodeData expected_node{42, server.getNamespaceIndex(), 9999, "everything", 
"Simulator/Default/Device1", {}};
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::OPCServerEndPoint.name,
 "opc.tcp://127.0.0.1:4840/"));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ParentNodeIDType.name,
 "Path"));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ParentNodeID.name,
 expected_node.path));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ParentNameSpaceIndex.name,
 std::to_string(expected_node.namespace_index)));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ValueType.name,
 "Int32"));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeIDType.name,
 "Int"));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeID.name,
 std::to_string(expected_node.node_id)));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeNameSpaceIndex.name,
 std::to_string(expected_node.namespace_index)));
+  
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeBrowseName.name,
 expected_node.browse_name));
+
+  // The first flow file creates the node.
+  const auto create_results = 
controller.trigger(std::to_string(expected_node.data));
+  REQUIRE(create_results.at(processors::PutOPCProcessor::Failure).empty());
+  REQUIRE(create_results.at(processors::PutOPCProcessor::Success).size() == 1);
+  verifyCreatedNode(expected_node, controller);
+
+  // A second flow file to the same target node must update it, not fail.
+  expected_node.data = 43;
+  const auto update_results = 
controller.trigger(std::to_string(expected_node.data));
+  REQUIRE(update_results.at(processors::PutOPCProcessor::Failure).empty());
+  REQUIRE(update_results.at(processors::PutOPCProcessor::Success).size() == 1);
+  verifyCreatedNode(expected_node, controller);
+}
+
 TEST_CASE("Test fetching using custom reference type id path", 
"[putopcprocessor]") {
   OpcUaTestServer server;
   server.start();
diff --git a/extensions/opc/tests/features/opcua.feature 
b/extensions/opc/tests/features/opcua.feature
index 0b6c8966d..d86ae5c59 100644
--- a/extensions/opc/tests/features/opcua.feature
+++ b/extensions/opc/tests/features/opcua.feature
@@ -61,9 +61,14 @@ Feature: Putting and fetching data to OPC UA server
     | Double       | 123.321 |
     | Boolean      | False   |
 
+  # The target node does not exist on the server, so the first flow file 
creates it and the second
+  # one - dropped into the input directory only after the first value has been 
read back - takes the
+  # update path. Sequencing the two writes through separate steps is what 
makes this deterministic:
+  # if both files were present at startup, GetFile's listing order would 
decide which value lands
+  # last and the final assertion would be a coin flip.
   Scenario Outline: Update and fetch data from an OPC UA node
     Given a GetFile processor with the "Input Directory" property set to 
"/tmp/input" in the "update-opc-ua-node" flow
-    And a directory at "/tmp/input" has a file with the content "<Value>" in 
the "update-opc-ua-node" flow
+    And a directory at "/tmp/input" has a file with the content "<Initial 
Value>" in the "update-opc-ua-node" flow
     And a PutOPCProcessor processor in the "update-opc-ua-node" flow
     And PutOPCProcessor is EVENT_DRIVEN in the "update-opc-ua-node" flow
     And a FetchOPCProcessor processor in the "fetch-opc-ua-node" flow
@@ -79,7 +84,7 @@ Feature: Putting and fetching data to OPC UA server
       | PutOPCProcessor   | Target node namespace index | 1                    
                             |
       | PutOPCProcessor   | Value type                  | <Value Type>         
                             |
       | PutOPCProcessor   | OPC server endpoint         | 
opc.tcp://opcua-server-${scenario_id}:4840/       |
-      | PutOPCProcessor   | Target node browse name     | testnodename         
                             |
+      | PutOPCProcessor   | Target node browse name     | updatetestnode       
                             |
     And these processor properties are set in the "fetch-opc-ua-node" flow
       | processor name    | property name               | property value       
                             |
       | FetchOPCProcessor | Node ID                     | <Node ID>            
                             |
@@ -94,17 +99,22 @@ Feature: Putting and fetching data to OPC UA server
     And an OPC UA server is set up
 
     When all instances start up
-    Then in the "fetch-opc-ua-node" container at least one file with the 
content "<Value>" is placed in the "/tmp/output" directory in less than 60 
seconds
+    # The node did not exist, so this first value proves the node was created.
+    Then in the "fetch-opc-ua-node" container at least one file with the 
content "<Initial Value>" is placed in the "/tmp/output" directory in less than 
60 seconds
 
-  # Node ids starting from 51000 are pre-defined demo node ids in the test 
server application (server_ctt) of the open62541 docker image. There is one 
nodeid defined
-  # for each type supported by OPC UA. These demo nodes can be used for 
testing purposes. "the.answer" is also a pre-defined string id for the same 
testing purposes.
-  Examples: Topic names and formats to test
-    | Node ID Type | Node ID     | Value Type   | Value       |
-    | Int          | 51034       | String       | minifi-test |
-    | Int          | 51001       | Boolean      | True        |
-    | String       | the.answer  | Int32        | 54          |
-    | Int          | 51019       | UInt32       | 123         |
-    | Int          | 51031       | Double       | 66.6        |
+    # Only now, with the node known to exist, write a second value to the same 
node. Reading this
+    # one back is only possible if PutOPCProcessor can update a node it 
created itself.
+    When a file with the content "<Updated Value>" is placed in "/tmp/input" 
in the "update-opc-ua-node" flow
+    Then in the "fetch-opc-ua-node" container at least one file with the 
content "<Updated Value>" is placed in the "/tmp/output" directory in less than 
60 seconds
+    And the logs of the "update-opc-ua-node" container do not contain the 
following message: "Failed to update node" after 1 second
+
+  Examples: Node id types and value types to test
+    | Node ID Type | Node ID     | Value Type   | Initial Value | Updated 
Value |
+    | Int          | 9001        | String       | minifi-test   | 
minifi-update |
+    | Int          | 9002        | Boolean      | False         | True         
 |
+    | String       | updatetest  | Int32        | 54            | 55           
 |
+    | Int          | 9003        | UInt32       | 123           | 456          
 |
+    | Int          | 9004        | Double       | 66.6          | 77.7         
 |
 
   Scenario: Create and fetch data from an OPC UA node through secure connection
     Given a GetFile processor with the "Input Directory" property set to 
"/tmp/input" in the "create-opc-ua-node" flow

Reply via email to