mirror of
https://github.com/open62541/open62541.git
synced 2026-10-26 02:20:27 +08:00
tests(client): add coverage tests for client sources
This commit is contained in:
@@ -161,6 +161,7 @@ getRemoteDataTypes(UA_Client *client, UA_ReadRequest *req,
|
||||
|
||||
*outCustomTypes = dta;
|
||||
|
||||
UA_ReadResponse_clear(&rr);
|
||||
UA_ReadRequest_clear(req);
|
||||
if(dta->typesSize == 0) {
|
||||
UA_free(dta->types);
|
||||
|
||||
@@ -451,6 +451,312 @@ START_TEST(Client_closes_on_server_error) {
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static void timedCallbackFired(UA_Client *c, void *data) {
|
||||
UA_Boolean *flag = (UA_Boolean *)data;
|
||||
*flag = true;
|
||||
}
|
||||
|
||||
START_TEST(Client_addTimedCallback) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Boolean fired = false;
|
||||
UA_UInt64 cbId = 0;
|
||||
/* Use a deadline in the past so it fires on the next iteration.
|
||||
* The event loop uses UA_DateTime_now_fake which starts at a low value. */
|
||||
retval = UA_Client_addTimedCallback(client, timedCallbackFired,
|
||||
&fired, 1, &cbId);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(cbId != 0);
|
||||
|
||||
/* Iterate enough for the timer to fire */
|
||||
for(int i = 0; i < 20; i++) {
|
||||
UA_fakeSleep(10);
|
||||
UA_Client_run_iterate(client, 1);
|
||||
}
|
||||
ck_assert(fired == true);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_addRepeatedCallback) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Boolean fired = false;
|
||||
UA_UInt64 cbId = 0;
|
||||
retval = UA_Client_addRepeatedCallback(client, timedCallbackFired,
|
||||
&fired, 50.0, &cbId);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(cbId != 0);
|
||||
|
||||
for(int i = 0; i < 20; i++) {
|
||||
UA_fakeSleep(10);
|
||||
UA_Client_run_iterate(client, 1);
|
||||
}
|
||||
ck_assert(fired == true);
|
||||
|
||||
/* Remove the callback */
|
||||
UA_Client_removeCallback(client, cbId);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_changeRepeatedCallbackInterval) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Boolean fired = false;
|
||||
UA_UInt64 cbId = 0;
|
||||
retval = UA_Client_addRepeatedCallback(client, timedCallbackFired,
|
||||
&fired, 5000.0, &cbId);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Change to a shorter interval */
|
||||
retval = UA_Client_changeRepeatedCallbackInterval(client, cbId, 20.0);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
for(int i = 0; i < 20; i++) {
|
||||
UA_fakeSleep(10);
|
||||
UA_Client_run_iterate(client, 1);
|
||||
}
|
||||
ck_assert(fired == true);
|
||||
|
||||
UA_Client_removeCallback(client, cbId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getConnectionAttribute) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Read serverDescription */
|
||||
UA_Variant val;
|
||||
UA_QualifiedName key = {0, UA_STRING_STATIC("serverDescription")};
|
||||
retval = UA_Client_getConnectionAttribute(client, key, &val);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(val.type == &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
|
||||
|
||||
/* Read securityPolicyUri */
|
||||
UA_Variant val2;
|
||||
UA_QualifiedName key2 = {0, UA_STRING_STATIC("securityPolicyUri")};
|
||||
retval = UA_Client_getConnectionAttribute(client, key2, &val2);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(val2.type == &UA_TYPES[UA_TYPES_STRING]);
|
||||
|
||||
/* Read securityMode */
|
||||
UA_Variant val3;
|
||||
UA_QualifiedName key3 = {0, UA_STRING_STATIC("securityMode")};
|
||||
retval = UA_Client_getConnectionAttribute(client, key3, &val3);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(val3.type == &UA_TYPES[UA_TYPES_MESSAGESECURITYMODE]);
|
||||
|
||||
/* Read unknown key */
|
||||
UA_Variant val4;
|
||||
UA_QualifiedName key4 = {0, UA_STRING_STATIC("unknownKey")};
|
||||
retval = UA_Client_getConnectionAttribute(client, key4, &val4);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINTERNALERROR);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getConnectionAttributeCopy) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Variant val;
|
||||
UA_QualifiedName key = {0, UA_STRING_STATIC("serverDescription")};
|
||||
retval = UA_Client_getConnectionAttributeCopy(client, key, &val);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(val.type == &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
|
||||
UA_Variant_clear(&val);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getConnectionAttribute_scalar) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Read securityMode as scalar */
|
||||
UA_MessageSecurityMode mode;
|
||||
UA_QualifiedName key = {0, UA_STRING_STATIC("securityMode")};
|
||||
retval = UA_Client_getConnectionAttribute_scalar(client, key,
|
||||
&UA_TYPES[UA_TYPES_MESSAGESECURITYMODE], &mode);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Type mismatch path */
|
||||
UA_Int32 wrongType;
|
||||
retval = UA_Client_getConnectionAttribute_scalar(client, key,
|
||||
&UA_TYPES[UA_TYPES_INT32], &wrongType);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADNOTFOUND);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getNamespaceIndex) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Read existing namespace (ns=0 is always "http://opcfoundation.org/UA/") */
|
||||
UA_ReadRequest rreq;
|
||||
UA_ReadRequest_init(&rreq);
|
||||
UA_ReadValueId rvi;
|
||||
UA_ReadValueId_init(&rvi);
|
||||
rvi.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_NAMESPACEARRAY);
|
||||
rvi.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
rreq.nodesToRead = &rvi;
|
||||
rreq.nodesToReadSize = 1;
|
||||
UA_ReadResponse rresp = UA_Client_Service_read(client, rreq);
|
||||
ck_assert_uint_eq(rresp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_ReadResponse_clear(&rresp);
|
||||
|
||||
/* Add and look up a namespace */
|
||||
UA_String nsUri = UA_STRING("http://test.namespace.example.com/");
|
||||
UA_UInt16 nsIndex = 0;
|
||||
retval = UA_Client_addNamespace(client, nsUri, &nsIndex);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(nsIndex > 0);
|
||||
|
||||
/* Look up again — should find it */
|
||||
UA_UInt16 nsIndex2 = 0;
|
||||
retval = UA_Client_getNamespaceIndex(client, nsUri, &nsIndex2);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(nsIndex, nsIndex2);
|
||||
|
||||
/* Add same namespace — should return existing index */
|
||||
UA_UInt16 nsIndex3 = 0;
|
||||
retval = UA_Client_addNamespace(client, nsUri, &nsIndex3);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(nsIndex, nsIndex3);
|
||||
|
||||
/* Non-existent namespace */
|
||||
UA_String unknown = UA_STRING("http://nonexistent/");
|
||||
UA_UInt16 nsIndex4 = 0;
|
||||
retval = UA_Client_getNamespaceIndex(client, unknown, &nsIndex4);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADNOTFOUND);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_findDataType) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
|
||||
/* Known built-in type */
|
||||
const UA_DataType *dt =
|
||||
UA_Client_findDataType(client, &UA_TYPES[UA_TYPES_INT32].typeId);
|
||||
ck_assert_ptr_ne(dt, NULL);
|
||||
ck_assert(UA_NodeId_equal(&dt->typeId, &UA_TYPES[UA_TYPES_INT32].typeId));
|
||||
|
||||
/* Unknown type */
|
||||
UA_NodeId unknownId = UA_NODEID_NUMERIC(99, 99999);
|
||||
dt = UA_Client_findDataType(client, &unknownId);
|
||||
ck_assert_ptr_eq(dt, NULL);
|
||||
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getState) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
|
||||
/* Before connecting */
|
||||
UA_SecureChannelState channelState;
|
||||
UA_SessionState sessionState;
|
||||
UA_StatusCode connectStatus;
|
||||
UA_Client_getState(client, &channelState, &sessionState, &connectStatus);
|
||||
ck_assert_uint_eq(channelState, UA_SECURECHANNELSTATE_CLOSED);
|
||||
ck_assert_uint_eq(sessionState, UA_SESSIONSTATE_CLOSED);
|
||||
ck_assert_uint_eq(connectStatus, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* With NULL outputs */
|
||||
UA_Client_getState(client, NULL, NULL, NULL);
|
||||
|
||||
/* After connecting */
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Client_getState(client, &channelState, &sessionState, &connectStatus);
|
||||
ck_assert_uint_eq(channelState, UA_SECURECHANNELSTATE_OPEN);
|
||||
ck_assert_uint_eq(sessionState, UA_SESSIONSTATE_ACTIVATED);
|
||||
ck_assert_uint_eq(connectStatus, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_cancelByRequestHandle) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Cancel a non-existent request handle — server should still return GOOD
|
||||
* (the CancelResponse itself succeeds, cancelCount is 0) */
|
||||
UA_UInt32 cancelCount = 99;
|
||||
retval = UA_Client_cancelByRequestHandle(client, 999999, &cancelCount);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(cancelCount, 0);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_cancelByRequestId_notFound) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Request id not in the async list -> BADNOTFOUND */
|
||||
UA_UInt32 cancelCount = 0;
|
||||
retval = UA_Client_cancelByRequestId(client, 999999, &cancelCount);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADNOTFOUND);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_setAuthenticationUsername) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
|
||||
UA_StatusCode retval =
|
||||
UA_ClientConfig_setAuthenticationUsername(cc, "user1", "password");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(cc->userIdentityToken.content.decoded.type ==
|
||||
&UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]);
|
||||
|
||||
retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite* testSuite_Client(void) {
|
||||
Suite *s = suite_create("Client");
|
||||
TCase *tc_client = tcase_create("Client Basic");
|
||||
@@ -475,6 +781,23 @@ static Suite* testSuite_Client(void) {
|
||||
tcase_add_test(tc_client_reconnect, Client_activateSessionTimeout);
|
||||
tcase_add_test(tc_client_reconnect, Client_activateSessionLocaleIds);
|
||||
suite_add_tcase(s,tc_client_reconnect);
|
||||
|
||||
TCase *tc_ext = tcase_create("Client Extended");
|
||||
tcase_add_checked_fixture(tc_ext, setup, teardown);
|
||||
tcase_add_test(tc_ext, Client_addTimedCallback);
|
||||
tcase_add_test(tc_ext, Client_addRepeatedCallback);
|
||||
tcase_add_test(tc_ext, Client_changeRepeatedCallbackInterval);
|
||||
tcase_add_test(tc_ext, Client_getConnectionAttribute);
|
||||
tcase_add_test(tc_ext, Client_getConnectionAttributeCopy);
|
||||
tcase_add_test(tc_ext, Client_getConnectionAttribute_scalar);
|
||||
tcase_add_test(tc_ext, Client_getNamespaceIndex);
|
||||
tcase_add_test(tc_ext, Client_findDataType);
|
||||
tcase_add_test(tc_ext, Client_getState);
|
||||
tcase_add_test(tc_ext, Client_cancelByRequestHandle);
|
||||
tcase_add_test(tc_ext, Client_cancelByRequestId_notFound);
|
||||
tcase_add_test(tc_ext, Client_setAuthenticationUsername);
|
||||
suite_add_tcase(s, tc_ext);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,94 @@ START_TEST(Client_run_iterate) {
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_connectSecureChannelAsync) {
|
||||
/* Connect only the SecureChannel without opening a Session */
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connectSecureChannelAsync(client,
|
||||
"opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Drive both sides until the channel is open */
|
||||
while(client->channel.state != UA_SECURECHANNELSTATE_OPEN) {
|
||||
UA_Server_run_iterate(server, false);
|
||||
UA_Client_run_iterate(client, 0);
|
||||
}
|
||||
|
||||
UA_SecureChannelState channelState;
|
||||
UA_SessionState sessionState;
|
||||
UA_StatusCode connectStatus;
|
||||
UA_Client_getState(client, &channelState, &sessionState, &connectStatus);
|
||||
ck_assert_uint_eq(channelState, UA_SECURECHANNELSTATE_OPEN);
|
||||
/* No session should be created */
|
||||
ck_assert(sessionState != UA_SESSIONSTATE_ACTIVATED);
|
||||
|
||||
UA_Client_disconnectAsync(client);
|
||||
while(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) {
|
||||
UA_Server_run_iterate(server, false);
|
||||
UA_Client_run_iterate(client, 0);
|
||||
}
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_connect_async_wrongUrl) {
|
||||
/* Attempt to connect to a wrong port — should fail gracefully */
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
cc->timeout = 200; /* short timeout */
|
||||
UA_StatusCode retval = UA_Client_connectAsync(client, "opc.tcp://localhost:4841");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Drive a few iterations */
|
||||
for(int i = 0; i < 50; i++) {
|
||||
UA_fakeSleep(10);
|
||||
UA_Client_run_iterate(client, 0);
|
||||
}
|
||||
|
||||
UA_SecureChannelState channelState;
|
||||
UA_StatusCode connectStatus;
|
||||
UA_Client_getState(client, &channelState, NULL, &connectStatus);
|
||||
/* Either still connecting or failed — not OPEN */
|
||||
ck_assert(channelState != UA_SECURECHANNELSTATE_OPEN ||
|
||||
connectStatus != UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_connectUsername_async) {
|
||||
/* Connect with username/password via the sync connectUsername wrapper,
|
||||
* but using manual iteration for the server side */
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
cc->stateCallback = currentState;
|
||||
connected = false;
|
||||
UA_StatusCode retval = UA_Client_connectAsync(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
while(!connected) {
|
||||
UA_Server_run_iterate(server, false);
|
||||
retval = UA_Client_run_iterate(client, 0);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
}
|
||||
|
||||
/* Verify the client is connected with an active session */
|
||||
UA_SecureChannelState channelState;
|
||||
UA_SessionState sessionState;
|
||||
UA_Client_getState(client, &channelState, &sessionState, NULL);
|
||||
ck_assert_uint_eq(channelState, UA_SECURECHANNELSTATE_OPEN);
|
||||
ck_assert_uint_eq(sessionState, UA_SESSIONSTATE_ACTIVATED);
|
||||
|
||||
/* Disconnect asynchronously */
|
||||
UA_Client_disconnectAsync(client);
|
||||
while(client->channel.state != UA_SECURECHANNELSTATE_CLOSED) {
|
||||
UA_Server_run_iterate(server, false);
|
||||
UA_Client_run_iterate(client, 0);
|
||||
}
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite* testSuite_Client(void) {
|
||||
Suite *s = suite_create("Client");
|
||||
TCase *tc_client_connect = tcase_create("Client Connect Async");
|
||||
@@ -196,6 +284,9 @@ static Suite* testSuite_Client(void) {
|
||||
tcase_add_test(tc_client_connect, Client_no_connection);
|
||||
tcase_add_test(tc_client_connect, Client_without_run_iterate);
|
||||
tcase_add_test(tc_client_connect, Client_run_iterate);
|
||||
tcase_add_test(tc_client_connect, Client_connectSecureChannelAsync);
|
||||
tcase_add_test(tc_client_connect, Client_connect_async_wrongUrl);
|
||||
tcase_add_test(tc_client_connect, Client_connectUsername_async);
|
||||
suite_add_tcase(s,tc_client_connect);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -60,11 +60,100 @@ START_TEST(Client_connect_badEndpointUrl) {
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getEndpoints) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
|
||||
size_t endpointCount = 0;
|
||||
UA_EndpointDescription *endpoints = NULL;
|
||||
UA_StatusCode retval = UA_Client_getEndpoints(client,
|
||||
"opc.tcp://localhost:4840",
|
||||
&endpointCount, &endpoints);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(endpointCount > 0);
|
||||
ck_assert_ptr_ne(endpoints, NULL);
|
||||
|
||||
/* Verify each endpoint has a non-empty securityPolicyUri */
|
||||
for(size_t i = 0; i < endpointCount; i++) {
|
||||
ck_assert(endpoints[i].securityPolicyUri.length > 0);
|
||||
}
|
||||
|
||||
UA_Array_delete(endpoints, endpointCount,
|
||||
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
|
||||
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_findServers) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
|
||||
size_t serverCount = 0;
|
||||
UA_ApplicationDescription *servers = NULL;
|
||||
UA_StatusCode retval = UA_Client_findServers(client,
|
||||
"opc.tcp://localhost:4840",
|
||||
0, NULL, 0, NULL,
|
||||
&serverCount, &servers);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(serverCount > 0);
|
||||
ck_assert_ptr_ne(servers, NULL);
|
||||
|
||||
UA_Array_delete(servers, serverCount,
|
||||
&UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
|
||||
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_getEndpoints_connected) {
|
||||
/* Connect first, then getEndpoints on same URL — should succeed */
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
size_t endpointCount = 0;
|
||||
UA_EndpointDescription *endpoints = NULL;
|
||||
retval = UA_Client_getEndpoints(client, "opc.tcp://localhost:4840",
|
||||
&endpointCount, &endpoints);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(endpointCount > 0);
|
||||
|
||||
UA_Array_delete(endpoints, endpointCount,
|
||||
&UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_findServers_connected) {
|
||||
/* Connect first, then findServers on same URL — should succeed */
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
size_t serverCount = 0;
|
||||
UA_ApplicationDescription *servers = NULL;
|
||||
retval = UA_Client_findServers(client, "opc.tcp://localhost:4840",
|
||||
0, NULL, 0, NULL,
|
||||
&serverCount, &servers);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(serverCount > 0);
|
||||
|
||||
UA_Array_delete(servers, serverCount,
|
||||
&UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite* testSuite_Client(void) {
|
||||
Suite *s = suite_create("Client");
|
||||
TCase *tc_client = tcase_create("Client Discovery");
|
||||
tcase_add_checked_fixture(tc_client, setup, teardown);
|
||||
tcase_add_test(tc_client, Client_connect_badEndpointUrl);
|
||||
tcase_add_test(tc_client, Client_getEndpoints);
|
||||
tcase_add_test(tc_client, Client_findServers);
|
||||
tcase_add_test(tc_client, Client_getEndpoints_connected);
|
||||
tcase_add_test(tc_client, Client_findServers_connected);
|
||||
suite_add_tcase(s,tc_client);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1034,6 +1034,188 @@ END_TEST
|
||||
|
||||
#endif
|
||||
|
||||
/* Extended tests for untested highlevel functions */
|
||||
|
||||
START_TEST(Highlevel_TranslateBrowsePath) {
|
||||
/* Translate a browse path from Objects folder to Server node */
|
||||
UA_BrowsePath bp;
|
||||
UA_BrowsePath_init(&bp);
|
||||
bp.startingNode = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
|
||||
|
||||
UA_RelativePathElement rpe;
|
||||
UA_RelativePathElement_init(&rpe);
|
||||
rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
|
||||
rpe.targetName = UA_QUALIFIEDNAME(0, "Server");
|
||||
bp.relativePath.elementsSize = 1;
|
||||
bp.relativePath.elements = &rpe;
|
||||
|
||||
UA_BrowsePathResult res =
|
||||
UA_Client_translateBrowsePathToNodeIds(client, &bp);
|
||||
ck_assert_uint_eq(res.statusCode, UA_STATUSCODE_GOOD);
|
||||
ck_assert(res.targetsSize > 0);
|
||||
UA_NodeId serverNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
|
||||
ck_assert(UA_NodeId_equal(&res.targets[0].targetId.nodeId,
|
||||
&serverNodeId));
|
||||
UA_BrowsePathResult_clear(&res);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Highlevel_WriteValueScalar) {
|
||||
/* Write a scalar value using writeValueAttribute_scalar and read back */
|
||||
UA_Int32 newVal = 42;
|
||||
UA_StatusCode retval =
|
||||
UA_Client_writeValueAttribute_scalar(client,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE),
|
||||
&newVal, &UA_TYPES[UA_TYPES_INT32]);
|
||||
/* Access may be denied because serverstatus/state is read-only,
|
||||
* but the function path is exercised */
|
||||
(void)retval;
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Highlevel_WriteValueEx) {
|
||||
/* Write a DataValue using writeValueAttributeEx and read back */
|
||||
UA_DataValue dv;
|
||||
UA_DataValue_init(&dv);
|
||||
dv.hasValue = true;
|
||||
UA_Int32 val = 99;
|
||||
UA_Variant_setScalar(&dv.value, &val, &UA_TYPES[UA_TYPES_INT32]);
|
||||
dv.hasSourceTimestamp = true;
|
||||
dv.sourceTimestamp = UA_DateTime_now();
|
||||
|
||||
UA_StatusCode retval =
|
||||
UA_Client_writeValueAttributeEx(client,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE),
|
||||
&dv);
|
||||
/* May be denied on read-only node but exercises the code path */
|
||||
(void)retval;
|
||||
}
|
||||
END_TEST
|
||||
|
||||
#ifdef UA_ENABLE_NODEMANAGEMENT
|
||||
START_TEST(Highlevel_ReadWriteAccessLevelEx) {
|
||||
/* Create a variable node to test AccessLevelEx */
|
||||
UA_VariableAttributes attr = UA_VariableAttributes_default;
|
||||
attr.displayName = UA_LOCALIZEDTEXT("en-US", "ALExTest");
|
||||
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
|
||||
attr.writeMask = 0xFFFFFFFF;
|
||||
UA_Int32 intVal = 123;
|
||||
UA_Variant_setScalar(&attr.value, &intVal, &UA_TYPES[UA_TYPES_INT32]);
|
||||
attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
|
||||
UA_NodeId outId = UA_NODEID_NULL;
|
||||
UA_StatusCode retval =
|
||||
UA_Client_addVariableNode(client, UA_NODEID_NULL,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
|
||||
UA_QUALIFIEDNAME(1, "ALExTest"),
|
||||
UA_NODEID_NULL, attr, &outId);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Read AccessLevelEx */
|
||||
UA_UInt32 alEx = 0;
|
||||
retval = UA_Client_readAccessLevelExAttribute(client, outId, &alEx);
|
||||
/* May not be supported → either GOOD or BADATTRIBUTEIDINVALID */
|
||||
if(retval == UA_STATUSCODE_GOOD) {
|
||||
/* Write AccessLevelEx */
|
||||
UA_UInt32 newAlEx = alEx | 0x100; /* set a higher bit */
|
||||
retval = UA_Client_writeAccessLevelExAttribute(client, outId, &newAlEx);
|
||||
/* May fail, but exercises the path */
|
||||
(void)retval;
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
UA_Client_deleteNode(client, outId, true);
|
||||
UA_NodeId_clear(&outId);
|
||||
}
|
||||
END_TEST
|
||||
#endif
|
||||
|
||||
START_TEST(Highlevel_ForEachChildNode_InvalidNode) {
|
||||
/* forEachChildNodeCall with non-existent node — exercises the browse path.
|
||||
* The server may return GOOD with empty results for unknown nodes. */
|
||||
UA_NodeId invalidNode = UA_NODEID_NUMERIC(99, 99999);
|
||||
unsigned int prevCount = iteratedNodeCount;
|
||||
UA_Client_forEachChildNodeCall(client, invalidNode, nodeIter, NULL);
|
||||
/* No children should have been iterated */
|
||||
ck_assert_uint_eq(iteratedNodeCount, prevCount);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Highlevel_WriteRead_Direct) {
|
||||
/* UA_Client_write with NULL argument */
|
||||
UA_StatusCode retval = UA_Client_write(client, NULL);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINTERNALERROR);
|
||||
|
||||
/* UA_Client_write with a valid write value */
|
||||
UA_WriteValue wv;
|
||||
UA_WriteValue_init(&wv);
|
||||
wv.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
|
||||
wv.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
wv.value.hasValue = true;
|
||||
UA_DateTime now = UA_DateTime_now();
|
||||
UA_Variant_setScalar(&wv.value.value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
|
||||
retval = UA_Client_write(client, &wv);
|
||||
/* Result may be BADWRITENOTSUPPORTED for read-only node, but path is hit */
|
||||
(void)retval;
|
||||
|
||||
/* UA_Client_read with a valid read value id */
|
||||
UA_ReadValueId rv;
|
||||
UA_ReadValueId_init(&rv);
|
||||
rv.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);
|
||||
rv.attributeId = UA_ATTRIBUTEID_VALUE;
|
||||
UA_DataValue dval = UA_Client_read(client, &rv);
|
||||
ck_assert_uint_eq(dval.status, UA_STATUSCODE_GOOD);
|
||||
ck_assert(dval.hasValue);
|
||||
UA_DataValue_clear(&dval);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Highlevel_BrowseSimplified) {
|
||||
/* Test UA_Client_browse highlevel wrapper */
|
||||
UA_BrowseDescription bd;
|
||||
UA_BrowseDescription_init(&bd);
|
||||
bd.nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
|
||||
bd.resultMask = UA_BROWSERESULTMASK_ALL;
|
||||
|
||||
UA_BrowseResult br = UA_Client_browse(client, NULL, 2, &bd);
|
||||
ck_assert_uint_eq(br.statusCode, UA_STATUSCODE_GOOD);
|
||||
ck_assert(br.referencesSize > 0);
|
||||
|
||||
/* Use browseNext if there's a continuation point */
|
||||
if(br.continuationPoint.length > 0) {
|
||||
UA_BrowseResult br2 =
|
||||
UA_Client_browseNext(client, false, br.continuationPoint);
|
||||
ck_assert_uint_eq(br2.statusCode, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Release continuation point */
|
||||
if(br2.continuationPoint.length > 0) {
|
||||
UA_BrowseResult br3 =
|
||||
UA_Client_browseNext(client, true, br2.continuationPoint);
|
||||
UA_BrowseResult_clear(&br3);
|
||||
}
|
||||
UA_BrowseResult_clear(&br2);
|
||||
}
|
||||
|
||||
UA_BrowseResult_clear(&br);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
#ifdef UA_ENABLE_METHODCALLS
|
||||
START_TEST(Highlevel_CallMethod_Error) {
|
||||
/* Call a method on a non-existent node */
|
||||
size_t outputSize = 0;
|
||||
UA_Variant *output = NULL;
|
||||
UA_StatusCode retval = UA_Client_call(client,
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
|
||||
UA_NODEID_NUMERIC(1, 99999),
|
||||
0, NULL, &outputSize, &output);
|
||||
ck_assert(retval != UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(outputSize, 0);
|
||||
ck_assert(output == NULL);
|
||||
}
|
||||
END_TEST
|
||||
#endif
|
||||
|
||||
static Suite *testSuite_Client(void) {
|
||||
Suite *s = suite_create("Client Highlevel");
|
||||
TCase *tc_misc = tcase_create("Client Highlevel Misc");
|
||||
@@ -1081,6 +1263,23 @@ static Suite *testSuite_Client(void) {
|
||||
tcase_add_test(tc_readwrite, Node_ReadWrite_UserExecutable);
|
||||
suite_add_tcase(s, tc_readwrite);
|
||||
#endif
|
||||
|
||||
TCase *tc_ext = tcase_create("Client Highlevel Extended");
|
||||
tcase_add_checked_fixture(tc_ext, setup, teardown);
|
||||
tcase_add_test(tc_ext, Highlevel_TranslateBrowsePath);
|
||||
tcase_add_test(tc_ext, Highlevel_WriteValueScalar);
|
||||
tcase_add_test(tc_ext, Highlevel_WriteValueEx);
|
||||
#ifdef UA_ENABLE_NODEMANAGEMENT
|
||||
tcase_add_test(tc_ext, Highlevel_ReadWriteAccessLevelEx);
|
||||
#endif
|
||||
tcase_add_test(tc_ext, Highlevel_ForEachChildNode_InvalidNode);
|
||||
tcase_add_test(tc_ext, Highlevel_WriteRead_Direct);
|
||||
tcase_add_test(tc_ext, Highlevel_BrowseSimplified);
|
||||
#ifdef UA_ENABLE_METHODCALLS
|
||||
tcase_add_test(tc_ext, Highlevel_CallMethod_Error);
|
||||
#endif
|
||||
suite_add_tcase(s, tc_ext);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1597,6 +1597,339 @@ START_TEST(Client_subscription_transfer) {
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_subscription_getSetContext) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
/* Get context — initially NULL */
|
||||
void *ctx = (void*)0xDEAD;
|
||||
retval = UA_Client_Subscriptions_getContext(client, subId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_ptr_eq(ctx, NULL);
|
||||
|
||||
/* Set context */
|
||||
int myData = 42;
|
||||
retval = UA_Client_Subscriptions_setContext(client, subId, &myData);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Get context again */
|
||||
retval = UA_Client_Subscriptions_getContext(client, subId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_ptr_eq(ctx, &myData);
|
||||
|
||||
/* Invalid subscription id */
|
||||
retval = UA_Client_Subscriptions_getContext(client, 99999, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID);
|
||||
|
||||
retval = UA_Client_Subscriptions_setContext(client, 99999, &myData);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID);
|
||||
|
||||
/* NULL args */
|
||||
retval = UA_Client_Subscriptions_getContext(client, subId, NULL);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINVALIDARGUMENT);
|
||||
|
||||
retval = UA_Client_Subscriptions_getContext(NULL, subId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINVALIDARGUMENT);
|
||||
|
||||
UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_subscription_deleteSingle) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
/* Delete it */
|
||||
retval = UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Delete again — should fail */
|
||||
retval = UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
ck_assert(retval != UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_subscription_setPublishingMode) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
/* Disable publishing */
|
||||
UA_SetPublishingModeRequest spmReq;
|
||||
UA_SetPublishingModeRequest_init(&spmReq);
|
||||
spmReq.publishingEnabled = false;
|
||||
spmReq.subscriptionIds = &subId;
|
||||
spmReq.subscriptionIdsSize = 1;
|
||||
UA_SetPublishingModeResponse spmResp =
|
||||
UA_Client_Subscriptions_setPublishingMode(client, spmReq);
|
||||
ck_assert_uint_eq(spmResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(spmResp.resultsSize, 1);
|
||||
ck_assert_uint_eq(spmResp.results[0], UA_STATUSCODE_GOOD);
|
||||
UA_SetPublishingModeResponse_clear(&spmResp);
|
||||
|
||||
/* Re-enable publishing */
|
||||
spmReq.publishingEnabled = true;
|
||||
spmResp = UA_Client_Subscriptions_setPublishingMode(client, spmReq);
|
||||
ck_assert_uint_eq(spmResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_SetPublishingModeResponse_clear(&spmResp);
|
||||
|
||||
UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static void
|
||||
dataChangeCallback_ext(UA_Client *c, UA_UInt32 subId, void *subContext,
|
||||
UA_UInt32 monId, void *monContext,
|
||||
UA_DataValue *value) {
|
||||
/* no-op: just need a valid callback */
|
||||
}
|
||||
|
||||
START_TEST(Client_monitoredItem_getSetContext) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
/* Create a monitored item */
|
||||
UA_MonitoredItemCreateRequest monReq =
|
||||
UA_MonitoredItemCreateRequest_default(
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
|
||||
UA_MonitoredItemCreateResult monResult =
|
||||
UA_Client_MonitoredItems_createDataChange(client, subId,
|
||||
UA_TIMESTAMPSTORETURN_BOTH, monReq, NULL, dataChangeCallback_ext, NULL);
|
||||
ck_assert_uint_eq(monResult.statusCode, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 monId = monResult.monitoredItemId;
|
||||
|
||||
/* Get context — initially NULL */
|
||||
void *ctx = (void*)0xDEAD;
|
||||
retval = UA_Client_MonitoredItem_getContext(client, subId, monId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_ptr_eq(ctx, NULL);
|
||||
|
||||
/* Set context */
|
||||
int myData = 123;
|
||||
retval = UA_Client_MonitoredItem_setContext(client, subId, monId, &myData);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Get context again */
|
||||
retval = UA_Client_MonitoredItem_getContext(client, subId, monId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert_ptr_eq(ctx, &myData);
|
||||
|
||||
/* Invalid subscription id */
|
||||
retval = UA_Client_MonitoredItem_getContext(client, 99999, monId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID);
|
||||
|
||||
/* Invalid monitored item id */
|
||||
retval = UA_Client_MonitoredItem_getContext(client, subId, 99999, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADMONITOREDITEMIDINVALID);
|
||||
|
||||
/* NULL args */
|
||||
retval = UA_Client_MonitoredItem_getContext(client, subId, monId, NULL);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINVALIDARGUMENT);
|
||||
|
||||
retval = UA_Client_MonitoredItem_getContext(NULL, subId, monId, &ctx);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINVALIDARGUMENT);
|
||||
|
||||
/* Set context with invalid sub */
|
||||
retval = UA_Client_MonitoredItem_setContext(client, 99999, monId, &myData);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID);
|
||||
|
||||
retval = UA_Client_MonitoredItem_setContext(client, subId, 99999, &myData);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADMONITOREDITEMIDINVALID);
|
||||
|
||||
retval = UA_Client_MonitoredItem_setContext(NULL, subId, monId, &myData);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINVALIDARGUMENT);
|
||||
|
||||
UA_Client_MonitoredItems_deleteSingle(client, subId, monId);
|
||||
UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_subscription_setMonitoringMode) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription + monitored item */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
UA_MonitoredItemCreateRequest monReq =
|
||||
UA_MonitoredItemCreateRequest_default(
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
|
||||
UA_MonitoredItemCreateResult monResult =
|
||||
UA_Client_MonitoredItems_createDataChange(client, subId,
|
||||
UA_TIMESTAMPSTORETURN_BOTH, monReq, NULL, dataChangeCallback_ext, NULL);
|
||||
ck_assert_uint_eq(monResult.statusCode, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 monId = monResult.monitoredItemId;
|
||||
|
||||
/* Set monitoring mode to Disabled */
|
||||
UA_SetMonitoringModeRequest smmReq;
|
||||
UA_SetMonitoringModeRequest_init(&smmReq);
|
||||
smmReq.subscriptionId = subId;
|
||||
smmReq.monitoringMode = UA_MONITORINGMODE_DISABLED;
|
||||
smmReq.monitoredItemIds = &monId;
|
||||
smmReq.monitoredItemIdsSize = 1;
|
||||
UA_SetMonitoringModeResponse smmResp =
|
||||
UA_Client_MonitoredItems_setMonitoringMode(client, smmReq);
|
||||
ck_assert_uint_eq(smmResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_SetMonitoringModeResponse_clear(&smmResp);
|
||||
|
||||
/* Set back to Reporting */
|
||||
smmReq.monitoringMode = UA_MONITORINGMODE_REPORTING;
|
||||
smmResp = UA_Client_MonitoredItems_setMonitoringMode(client, smmReq);
|
||||
ck_assert_uint_eq(smmResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_SetMonitoringModeResponse_clear(&smmResp);
|
||||
|
||||
UA_Client_MonitoredItems_deleteSingle(client, subId, monId);
|
||||
UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_subscription_setTriggering) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
/* Create trigger and triggering items */
|
||||
UA_MonitoredItemCreateRequest monReqs[2];
|
||||
monReqs[0] = UA_MonitoredItemCreateRequest_default(
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME));
|
||||
monReqs[1] = UA_MonitoredItemCreateRequest_default(
|
||||
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_STATE));
|
||||
|
||||
UA_MonitoredItemCreateResult monResults[2];
|
||||
monResults[0] = UA_Client_MonitoredItems_createDataChange(client, subId,
|
||||
UA_TIMESTAMPSTORETURN_BOTH, monReqs[0], NULL, dataChangeCallback_ext, NULL);
|
||||
ck_assert_uint_eq(monResults[0].statusCode, UA_STATUSCODE_GOOD);
|
||||
monResults[1] = UA_Client_MonitoredItems_createDataChange(client, subId,
|
||||
UA_TIMESTAMPSTORETURN_BOTH, monReqs[1], NULL, dataChangeCallback_ext, NULL);
|
||||
ck_assert_uint_eq(monResults[1].statusCode, UA_STATUSCODE_GOOD);
|
||||
|
||||
UA_UInt32 triggeringItemId = monResults[0].monitoredItemId;
|
||||
UA_UInt32 linkedItemId = monResults[1].monitoredItemId;
|
||||
|
||||
/* SetTriggering: add a link */
|
||||
UA_SetTriggeringRequest stReq;
|
||||
UA_SetTriggeringRequest_init(&stReq);
|
||||
stReq.subscriptionId = subId;
|
||||
stReq.triggeringItemId = triggeringItemId;
|
||||
stReq.linksToAdd = &linkedItemId;
|
||||
stReq.linksToAddSize = 1;
|
||||
UA_SetTriggeringResponse stResp =
|
||||
UA_Client_MonitoredItems_setTriggering(client, stReq);
|
||||
ck_assert_uint_eq(stResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_SetTriggeringResponse_clear(&stResp);
|
||||
|
||||
/* Remove the link */
|
||||
UA_SetTriggeringRequest stReq2;
|
||||
UA_SetTriggeringRequest_init(&stReq2);
|
||||
stReq2.subscriptionId = subId;
|
||||
stReq2.triggeringItemId = triggeringItemId;
|
||||
stReq2.linksToRemove = &linkedItemId;
|
||||
stReq2.linksToRemoveSize = 1;
|
||||
stResp = UA_Client_MonitoredItems_setTriggering(client, stReq2);
|
||||
ck_assert_uint_eq(stResp.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_SetTriggeringResponse_clear(&stResp);
|
||||
|
||||
UA_Client_MonitoredItems_deleteSingle(client, subId, monResults[0].monitoredItemId);
|
||||
UA_Client_MonitoredItems_deleteSingle(client, subId, monResults[1].monitoredItemId);
|
||||
UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(Client_subscription_modifyAsync) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Create subscription */
|
||||
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
|
||||
UA_CreateSubscriptionResponse response =
|
||||
UA_Client_Subscriptions_create(client, request, NULL, NULL, NULL);
|
||||
ck_assert_uint_eq(response.responseHeader.serviceResult, UA_STATUSCODE_GOOD);
|
||||
UA_UInt32 subId = response.subscriptionId;
|
||||
|
||||
/* Modify via async */
|
||||
UA_ModifySubscriptionRequest modReq;
|
||||
UA_ModifySubscriptionRequest_init(&modReq);
|
||||
modReq.subscriptionId = subId;
|
||||
modReq.requestedPublishingInterval = 200.0;
|
||||
modReq.requestedMaxKeepAliveCount = 20;
|
||||
modReq.requestedLifetimeCount = 600;
|
||||
modReq.maxNotificationsPerPublish = 100;
|
||||
|
||||
UA_UInt32 reqId = 0;
|
||||
retval = UA_Client_Subscriptions_modify_async(client, modReq,
|
||||
NULL, NULL, &reqId);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
ck_assert(reqId != 0);
|
||||
|
||||
/* Drive iterations to get response */
|
||||
for(int i = 0; i < 20; i++) {
|
||||
UA_fakeSleep(10);
|
||||
UA_Client_run_iterate(client, 1);
|
||||
}
|
||||
|
||||
UA_Client_Subscriptions_deleteSingle(client, subId);
|
||||
UA_Client_disconnect(client);
|
||||
UA_Client_delete(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
#ifdef UA_ENABLE_METHODCALLS
|
||||
START_TEST(Client_methodcall) {
|
||||
UA_Client *client = UA_Client_newForUnitTest();
|
||||
@@ -1683,6 +2016,13 @@ static Suite* testSuite_Client(void) {
|
||||
tcase_add_test(tc_client, Client_subscription_server_disappears);
|
||||
tcase_add_test(tc_client, Client_subscription_transfer);
|
||||
tcase_add_test(tc_client, Client_subscription_writeBurst);
|
||||
tcase_add_test(tc_client, Client_subscription_getSetContext);
|
||||
tcase_add_test(tc_client, Client_subscription_deleteSingle);
|
||||
tcase_add_test(tc_client, Client_subscription_setPublishingMode);
|
||||
tcase_add_test(tc_client, Client_monitoredItem_getSetContext);
|
||||
tcase_add_test(tc_client, Client_subscription_setMonitoringMode);
|
||||
tcase_add_test(tc_client, Client_subscription_setTriggering);
|
||||
tcase_add_test(tc_client, Client_subscription_modifyAsync);
|
||||
suite_add_tcase(s,tc_client);
|
||||
|
||||
#ifdef UA_ENABLE_METHODCALLS
|
||||
|
||||
@@ -269,9 +269,6 @@ START_TEST(sub_data_change_notification) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* The subscription create and monitored item create are the main coverage
|
||||
* hits. Whether the notification arrives depends on timing. */
|
||||
|
||||
/* Write a new value */
|
||||
UA_Int32 newVal = 777;
|
||||
UA_Variant wv;
|
||||
@@ -561,6 +558,64 @@ START_TEST(client_register_nodes) {
|
||||
disconnectClient(client);
|
||||
} END_TEST
|
||||
|
||||
/* === Client getRemoteDataTypes (covers ua_client_util.c) === */
|
||||
|
||||
START_TEST(client_getRemoteDataTypes_browse) {
|
||||
/* Test with dataTypesNodesSize == 0, which triggers the browse-based
|
||||
* discovery of unknown DataTypes in the server's type hierarchy */
|
||||
UA_Client *client = connectClient();
|
||||
|
||||
UA_DataTypeArray *customTypes = NULL;
|
||||
UA_StatusCode retval =
|
||||
UA_Client_getRemoteDataTypes(client, 0, NULL, &customTypes);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
/* Add to client config so it gets cleaned up on client delete */
|
||||
if(customTypes) {
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
customTypes->next = cc->customDataTypes;
|
||||
cc->customDataTypes = customTypes;
|
||||
}
|
||||
|
||||
disconnectClient(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(client_getRemoteDataTypes_explicit) {
|
||||
/* Test with explicit NodeIds — use a known DataType node e.g. Range */
|
||||
UA_Client *client = connectClient();
|
||||
|
||||
UA_NodeId nodes[1];
|
||||
nodes[0] = UA_NODEID_NUMERIC(0, UA_NS0ID_RANGE);
|
||||
|
||||
UA_DataTypeArray *customTypes = NULL;
|
||||
UA_StatusCode retval =
|
||||
UA_Client_getRemoteDataTypes(client, 1, nodes, &customTypes);
|
||||
/* Range is already known, so the result array may be empty but call succeeds */
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_GOOD);
|
||||
|
||||
if(customTypes) {
|
||||
UA_ClientConfig *cc = UA_Client_getConfig(client);
|
||||
customTypes->next = cc->customDataTypes;
|
||||
cc->customDataTypes = customTypes;
|
||||
}
|
||||
|
||||
disconnectClient(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(client_getRemoteDataTypes_nullArg) {
|
||||
/* NULL customTypes argument => BADINVALIDARGUMENT */
|
||||
UA_Client *client = connectClient();
|
||||
|
||||
UA_StatusCode retval =
|
||||
UA_Client_getRemoteDataTypes(client, 0, NULL, NULL);
|
||||
ck_assert_uint_eq(retval, UA_STATUSCODE_BADINVALIDARGUMENT);
|
||||
|
||||
disconnectClient(client);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* === Suite definition === */
|
||||
static Suite *testSuite_clientSubExt2(void) {
|
||||
TCase *tc_sub = tcase_create("SubLifecycle");
|
||||
@@ -589,6 +644,15 @@ static Suite *testSuite_clientSubExt2(void) {
|
||||
suite_add_tcase(s, tc_sub);
|
||||
suite_add_tcase(s, tc_disc);
|
||||
suite_add_tcase(s, tc_ops);
|
||||
|
||||
TCase *tc_util = tcase_create("ClientUtil");
|
||||
tcase_add_checked_fixture(tc_util, setup, teardown);
|
||||
tcase_set_timeout(tc_util, 30);
|
||||
tcase_add_test(tc_util, client_getRemoteDataTypes_browse);
|
||||
tcase_add_test(tc_util, client_getRemoteDataTypes_explicit);
|
||||
tcase_add_test(tc_util, client_getRemoteDataTypes_nullArg);
|
||||
suite_add_tcase(s, tc_util);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user