From bd1df8aae58cd023dd1e4b17767e237dade2c0f9 Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Wed, 13 May 2026 17:01:10 +0530 Subject: [PATCH 1/6] Update rrdEventProcess.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: remove unclear inline note from static profile fallback path Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/ddda5e68-d84d-4a89-ab64-02935f00a57f Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> chore: plan length-validation update for static fallback Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efaba2d2-7e7e-4927-bbe3-106dbb286e2b Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> fix: validate issue node length before installed-package fallback Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efaba2d2-7e7e-4927-bbe3-106dbb286e2b Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> chore: clarify installed-package length guard semantics Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efaba2d2-7e7e-4927-bbe3-106dbb286e2b Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> chore: align length-limit wording with validation semantics Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efaba2d2-7e7e-4927-bbe3-106dbb286e2b Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> chore: improve fallback guard naming and failure logging Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efaba2d2-7e7e-4927-bbe3-106dbb286e2b Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> refactor: centralize issue length and fallback skip logging Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efaba2d2-7e7e-4927-bbe3-106dbb286e2b Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdDynamic.c Update rrdDynamic.c Update rrdEventProcess.c Update rrdCommon.h Update rrdDynamic.c Refactor issueTypeSplitter to include suffix handling Update rrdEventProcess.c Update rrdExecuteScript.c Refactor rrdEventProcess.h to rrdExecuteScript.h Update rrdExecuteScript.h Update rrdExecuteScript.h Update rrdEventProcess.h Update rrdExecuteScript.h Update rrdJsonParser.c Update rrdJsonParser.h Update rrdInterface.c Update rrdCommon.h Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Fix indentation for appendMode assignment Update rrdCommon.h Fix appendMode assignment in rrdEventProcess.c Update rrdEventProcess.c Update rrdJsonParser.c Update rrdEventProcess.c Update rrdExecuteScript.h Update rrdExecuteScript.c Update rrdExecuteScript.c Update rrdExecuteScript.c Remove redundant logging from rrdJsonParser Update rrdJsonParser.c Update rrdEventProcess.c Update rrdJsonParser.c Update rrdEventProcess.c Update rrdJsonParser.c Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Update rrdJsonParser.c Fix heap overflow in issueTypeSplitter and memory leaks in suffix handling Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/fbc52780-966b-4912-825f-3030aa43c3e9 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdUnitTestRunner.cpp Delete .gitignore Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdUnitTestRunner.cpp Update rrdEventProcess.c Update rrdUnitTestRunner.cpp Update rrdUnitTestRunner.cpp Add gtest test cases for split_issue_type Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/9996d741-248e-4e58-8689-b4ba873cfaf2 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Remove build artifacts, add .gitignore Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/9996d741-248e-4e58-8689-b4ba873cfaf2 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Add explicit truncated content assertion in SuffixTruncatedWhenTooSmall test Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/9996d741-248e-4e58-8689-b4ba873cfaf2 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Update rrdEventProcess.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdJsonParser.c Update rrdJsonParser.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdUnitTestRunner.cpp Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Delete .gitignore Delete src/unittest/UTJson/device.properties Update rrdEventProcess.c Add gtest tests for split_issue_type, suffix field, and processIssueTypeEvent Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/76cad72a-f67f-4c05-8fb5-bfadf0c173b3 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Delete .gitignore Validate suffix prefix: only _Search- and _LogSearch- are allowed Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/125526c2-7b70-48f5-8bec-fd725eea8a04 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Base never contains underscore: split at first _ and discard invalid suffix Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/904bf10d-546b-4038-a60c-9bc76094a225 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdEventProcess.c Update rrdUnitTestRunner.cpp Update rrdEventProcess.c Update rrdEventProcess.c Fix segfault in GTEST_ENABLE mode when rbuf->jsonPath is NULL Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efdc0d6b-89e6-4423-b202-a900f5683839 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Remove accidentally committed dummy directory and update .gitignore Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/efdc0d6b-89e6-4423-b202-a900f5683839 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Fix IssueTypeSplitterTest to match new issueTypeSplitter behavior (no built-in special-char removal) Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/00abcaba-8a41-4b88-ae12-07b5ff780ff9 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Fix empty/whitespace IssueType bypassing processIssueTypeEvent guards Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/4de31e24-70d8-496c-ac6a-a5376771d936 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> fix: preserve hyphens in archive filename so portal can parse it correctly rrd_logproc_convert_issue_type() was converting '-' to '_', turning the suffix '_Search-67768-67' into '_SEARCH_67768_67'. This added extra '_' separators into the archive filename that broke the analytics portal's filename parser — it could no longer identify the timestamp field, so download requests used an incorrect S3 key. Fix: keep '-' as '-' in the sanitized output. The archive filename now uses '_' to separate structural fields (MAC, issueType, timestamp) and '-' within the UUID suffix, giving the portal a reliable delimiter. Also increase issue_type_sanitized buffer in uploadRRDLogs.c from 64 to 256 bytes so a full UUID suffix never causes a silent truncation failure. Before: 04B86A12F9F8_DEVICE_DEVICEIP_SEARCH_67768_67__RRD_DEBUG_LOGS.tgz After: 04B86A12F9F8_DEVICE_DEVICEIP_SEARCH-67768-67__RRD_DEBUG_LOGS.tgz All 331 tests pass. Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/981b6bc1-c9d2-4150-9e9d-851004942ffc Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> fix: replace _Search-/_LogSearch- prefix check with suffix length <= 9 rule split_issue_type() now discards any suffix whose total length (including the leading '_') exceeds 9 characters. The old _Search-/_LogSearch- prefix validation is removed entirely. Rule: strlen(underscore) <= 9 → suffix accepted strlen(underscore) > 9 → suffix discarded This means: - Short session tokens (e.g. "_ab12345", 8 chars) are carried through - Long UUID-based suffixes like "_Search-b6877385-...) are discarded, preventing extra '_' delimiters from breaking the portal filename parser Affected tests updated: - UnderscoreSplitsBaseAndSuffix : uses a short accepted suffix - MultipleUnderscoresSplitsAtFirst: "_def_ghi" (8 chars) now kept - BaseTruncatedWhenTooSmall / ExactFitBase: "_suffix" (7 chars) now kept - SuffixTruncatedWhenTooSmall: uses 9-char suffix with 5-byte buffer - OnlyUnderscoreInput: "_" (1 char) now kept - LogSearchSuffixIsValid → NineCharSuffixIsAccepted (boundary test) - SearchSuffixIsValid → LongSuffixIsDiscarded - InvalidSuffixPrefixDiscarded / SearchWithoutHyphenIsInvalid / LogSearchWithoutHyphenIsInvalid: updated comments (same outcomes) - ProcessIssueTypeEvntTest comments updated to reflect new rule All 331 tests pass. Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c6d6fc29-da2a-46af-a417-c2de67a18448 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> refactor: improve RRD_MAX_SUFFIX_LEN comment and rename length-based tests - Expand the RRD_MAX_SUFFIX_LEN comment to explain why 9 chars is the limit - Rename misleading test names that referenced old prefix-based validation: InvalidSuffixPrefixDiscarded → SuffixExceedingMaxLengthDiscarded SearchWithoutHyphenIsInvalid → SuffixSeventeenCharsDiscarded LogSearchWithoutHyphenIsInvalid → SuffixTwentyCharsDiscarded - LongSuffixIsDiscarded: switch to a neutral _1234567890 example so the test does not imply any Search-specific behavior All 331 tests pass. Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c6d6fc29-da2a-46af-a417-c2de67a18448 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdJsonParser.c Update uploadRRDLogs.c Update uploadRRDLogs.c Update rrdJsonParser.c Delete .gitignore Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdEventProcess.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdJsonParser.c Update rrdEventProcess.c sanitize split_issue_type suffix to [A-Za-z0-9_-] to prevent injection Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/72e164ed-ae53-4076-8fb1-5ff1e21375e9 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdEventProcess.c Delete .gitignore Update rrdRunCmdThread.c Update rrdRunCmdThread.h Delete src/unittest/UTJson/device.properties Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdRunCmdThread.c Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Update rrdDynamic.c RRD 1.3.4 release changelog updates Update rrdDynamic.c Update rrdDynamic.c Update rrdDynamic.c Update rrdDynamic.c fix: harden profile length guard null checks and cleanup Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c9da16b1-8b0f-497a-aca2-ec47fae37e48 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> chore: tighten strnlen boundary check and indentation Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c9da16b1-8b0f-497a-aca2-ec47fae37e48 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> fix: use bounded strnlen check and null freed issue pointers Agent-Logs-Url: https://github.com/rdkcentral/remote_debugger/sessions/c9da16b1-8b0f-497a-aca2-ec47fae37e48 Co-authored-by: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Update rrdDynamic.c Update rrdDynamic.c Update rrdDynamic.c Update rrdDynamic.c Update rrdDynamic.c --- CHANGELOG.md | 9 + src/rrdCommon.h | 2 + src/rrdDynamic.c | 25 +- src/rrdEventProcess.c | 78 +++++- src/rrdInterface.c | 5 + src/rrdJsonParser.c | 135 ++++++++++- src/rrdJsonParser.h | 2 + src/rrdRunCmdThread.c | 21 +- src/rrdRunCmdThread.h | 1 + src/rrd_logproc.c | 3 +- src/unittest/rrdUnitTestRunner.cpp | 376 ++++++++++++++++++++++++++++- 11 files changed, 628 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9247755..6e4641101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.3.4](https://github.com/rdkcentral/remote_debugger/compare/1.3.3...1.3.4) + +- RDK-60236 : Remote Debugger Supports UUID Information in Debug Report File [`#194`](https://github.com/rdkcentral/remote_debugger/pull/194) +- Refactor issueTypeSplitter to include suffix handling [`c56fef3`](https://github.com/rdkcentral/remote_debugger/commit/c56fef33df07f47c2ea91c26010548bf0a3baa36) +- Merge tag '1.3.3' into develop [`7151d20`](https://github.com/rdkcentral/remote_debugger/commit/7151d208ced521de7ee7dee66cca6aea951e95b3) + #### [1.3.3](https://github.com/rdkcentral/remote_debugger/compare/1.3.2...1.3.3) +> 23 April 2026 + - RDK-59833 : Remote Debugger datamodel support to retrieve Static Profile Data [`#183`](https://github.com/rdkcentral/remote_debugger/pull/183) - RDKEMW-16897: Add Agentic Support for the RRD [`#182`](https://github.com/rdkcentral/remote_debugger/pull/182) +- RRD 1.3.3 release changelog updates [`bdaea9a`](https://github.com/rdkcentral/remote_debugger/commit/bdaea9aa695da5b0c0450cddd096052bfd5b0ce3) - Merge tag '1.3.2' into develop [`8f793e6`](https://github.com/rdkcentral/remote_debugger/commit/8f793e69b6b2b91a27c6a8148014207788503efa) #### [1.3.2](https://github.com/rdkcentral/remote_debugger/compare/1.3.1...1.3.2) diff --git a/src/rrdCommon.h b/src/rrdCommon.h index b91ff6f7b..191ee4745 100644 --- a/src/rrdCommon.h +++ b/src/rrdCommon.h @@ -58,6 +58,7 @@ extern "C" #define RRD_MEDIA_APPS "/media/apps/" #define RDM_PKG_PREFIX "RDK-RRD-" #define RDM_PKG_SUFFIX ":1.0" +#define RRD_DYNAMIC_PROFILE_MAX_LENGTH 34 #ifndef RRD_PROFILE_LIST #define RRD_DEVICE_PROFILE "" @@ -97,6 +98,7 @@ typedef struct mbuffer { bool inDynamic; bool appendMode; deepsleep_event_et dsEvent; + char *suffix; // Holds the suffix split from issue type string, if any } data_buf; /*Structure for Message Header*/ diff --git a/src/rrdDynamic.c b/src/rrdDynamic.c index 2deee32ab..2ca6038d8 100644 --- a/src/rrdDynamic.c +++ b/src/rrdDynamic.c @@ -127,7 +127,20 @@ int RRDGetProfileStringLength(issueNodeData *pissueStructNode, bool isDeepSleepA unsigned int prefixlen = strlen(RDM_PKG_PREFIX); unsigned int suffixlen = strlen(RDM_PKG_SUFFIX); unsigned int nodelen = 0; + size_t boundedNodeLen = 0; + if ((pissueStructNode == NULL) || (pissueStructNode->Node == NULL)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Invalid issue node data for profile length calculation\n", __FUNCTION__, __LINE__); + return -1; + } + + boundedNodeLen = strnlen(pissueStructNode->Node, RRD_DYNAMIC_PROFILE_MAX_LENGTH + 1); + if (boundedNodeLen > RRD_DYNAMIC_PROFILE_MAX_LENGTH) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Issue node length must be less than %d\n", __FUNCTION__, __LINE__, RRD_DYNAMIC_PROFILE_MAX_LENGTH); + return -1; + } /* Calculate Length for Device Type for Deep Sleep Awake Event*/ if (isDeepSleepAwakeEvent) { @@ -143,7 +156,7 @@ int RRDGetProfileStringLength(issueNodeData *pissueStructNode, bool isDeepSleepA } else { - nodelen = strlen(pissueStructNode->Node); + nodelen = boundedNodeLen; length = prefixlen + nodelen + suffixlen; } return length + 1; @@ -270,9 +283,15 @@ void RRDRdmManagerDownloadRequest(issueNodeData *pissueStructNode, char *dynJSON { RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Memory Allocation Failed for Request RDM Manager Download.\n", __FUNCTION__, __LINE__); } - free(pissueStructNode->Node); - free(pissueStructNode->subNode); } + else + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Invalid profile length, skipping download request\n", __FUNCTION__, __LINE__); + } + free(pissueStructNode->Node); + free(pissueStructNode->subNode); + pissueStructNode->Node = NULL; + pissueStructNode->subNode = NULL; } else { diff --git a/src/rrdEventProcess.c b/src/rrdEventProcess.c index 5164e7832..eb3c882df 100644 --- a/src/rrdEventProcess.c +++ b/src/rrdEventProcess.c @@ -79,7 +79,35 @@ void processIssueTypeEvent(data_buf *rbuf) cmdBuff = (data_buf *)malloc(sizeof(data_buf)); if (cmdBuff) { - dataMsgLen = strlen(cmdMap[index]) + 1; + char base[128] = {0}; + char local_suffix[128] = {0}; + split_issue_type(cmdMap[index], base, sizeof(base), local_suffix, sizeof(local_suffix)); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Empty issue type after parsing token [%s], skipping... \n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + if (cmdMap[index]) + { + free(cmdMap[index]); + cmdMap[index] = NULL; + } + continue; + } + removeSpecialCharacterfromIssueTypeList(base); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Empty base after sanitization for token [%s], skipping... \n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + if (cmdMap[index]) + { + free(cmdMap[index]); + cmdMap[index] = NULL; + } + continue; + } + dataMsgLen = strlen(base) + 1; RRD_data_buff_init(cmdBuff, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); /* Setting Deafult Values*/ cmdBuff->inDynamic = rbuf->inDynamic; if(cmdBuff->inDynamic) @@ -88,9 +116,19 @@ void processIssueTypeEvent(data_buf *rbuf) } cmdBuff->appendMode = rbuf->appendMode; cmdBuff->mdata = (char *)calloc(1, dataMsgLen); + + /* Store suffix for this issue type */ + cmdBuff->suffix = NULL; + if (local_suffix[0] != '\0') { + cmdBuff->suffix = strdup(local_suffix); + if (cmdBuff->suffix == NULL) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to allocate memory for suffix... \n", __FUNCTION__, __LINE__); + } + } if (cmdBuff->mdata) { - strncpy((char *)cmdBuff->mdata, cmdMap[index], dataMsgLen); + strncpy((char *)cmdBuff->mdata, base, dataMsgLen); processIssueType(cmdBuff); } else @@ -99,6 +137,11 @@ void processIssueTypeEvent(data_buf *rbuf) } if(cmdBuff) { + if (cmdBuff->suffix) + { + free(cmdBuff->suffix); + cmdBuff->suffix = NULL; + } free(cmdBuff); cmdBuff = NULL; } @@ -392,7 +435,10 @@ static void processIssueTypeInStaticProfile(data_buf *rbuf, issueNodeData *pIssu #if !defined(GTEST_ENABLE) jsonParsed = readAndParseJSON(RRD_JSON_FILE); #else - jsonParsed = readAndParseJSON(rbuf->jsonPath); + if (rbuf->jsonPath != NULL) + { + jsonParsed = readAndParseJSON(rbuf->jsonPath); + } #endif if (jsonParsed == NULL) { // Static Profile JSON Parsing or Read Fail @@ -555,6 +601,29 @@ static void processIssueTypeInInstalledPackage(data_buf *rbuf, issueNodeData *pI suffixlen = strlen(RDM_PKG_SUFFIX); dynJSONPath = (char *)malloc(persistentAppslen + prefixlen + suffixlen + strlen(pIssueNode->Node) + rrdjsonlen + 1); #else + if ((rbuf == NULL) || (rbuf->jsonPath == NULL)) + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: jsonPath is NULL, skipping installed package check... \n", __FUNCTION__, __LINE__); + if (rbuf != NULL) + { + if (rbuf->mdata != NULL) + { + free(rbuf->mdata); + rbuf->mdata = NULL; + } + if (rbuf->suffix != NULL) + { + free(rbuf->suffix); + rbuf->suffix = NULL; + } + if (rbuf->jsonPath != NULL) + { + free(rbuf->jsonPath); + rbuf->jsonPath = NULL; + } + } + return; + } int utjsonlen = strlen(rbuf->jsonPath); dynJSONPath = (char *)malloc(utjsonlen + 1); #endif @@ -639,7 +708,7 @@ static void removeSpecialCharacterfromIssueTypeList(char *str) while (str[source] != '\0') { - if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') + if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') { str[destination] = str[source]; ++destination; @@ -663,7 +732,6 @@ static int issueTypeSplitter(char *input_str, const char delimeter, char ***args int cnt = 1, i = 0; char *str = input_str; - removeSpecialCharacterfromIssueTypeList(str); while (*str == delimeter) str++; diff --git a/src/rrdInterface.c b/src/rrdInterface.c index b69dd8936..30291cd84 100644 --- a/src/rrdInterface.c +++ b/src/rrdInterface.c @@ -275,6 +275,7 @@ void RRD_data_buff_init(data_buf *sbuf, message_type_et sndtype, deepsleep_event sbuf->inDynamic = false; sbuf->appendMode = false; sbuf->dsEvent = deepSleepEvent; + sbuf->suffix = NULL; } /*Function: RRD_data_buff_deAlloc @@ -295,6 +296,10 @@ void RRD_data_buff_deAlloc(data_buf *sbuf) { free(sbuf->jsonPath); } + if (sbuf->suffix) + { + free(sbuf->suffix); + } free(sbuf); } } diff --git a/src/rrdJsonParser.c b/src/rrdJsonParser.c index e06d93ac2..2fce93676 100644 --- a/src/rrdJsonParser.c +++ b/src/rrdJsonParser.c @@ -24,6 +24,8 @@ #include #include #include +/* Maximum suffix length, including the leading '_' character. */ +#define RRD_MAX_SUFFIX_LEN 9 /* * @function removeSpecialChar @@ -46,6 +48,102 @@ void removeSpecialChar(char *str) } } + +/* + * @function split_issue_type + * @brief Utility to split base and suffix from issue type string. + * The input is always split at the first '_'. The base is the part + * before the underscore (never contains '_'). The suffix is only + * kept when its total length (including the leading '_') is at most + * RRD_MAX_SUFFIX_LEN (9) characters; longer suffixes are discarded. + * After length validation the suffix is sanitized: only the characters + * [A-Za-z0-9_-] are retained so that the value is safe to use in file + * names and shell command arguments without risk of injection. + * If no underscore is present the full input is the base. + * Examples: + * "Device.DeviceTime_ab12345" → base="Device.DeviceTime", + * suffix="_ab12345" (8 chars, accepted) + * "Device.DeviceTime_Search-uuid-very-long" + * → base="Device.DeviceTime", + * suffix="" (>9 chars, discarded) + * "Device.DeviceTime" → base="Device.DeviceTime", + * suffix="" + * "Device.DeviceTime_ab;rm" → base="Device.DeviceTime", + * suffix="_abrm" (unsafe ';' stripped) + * @param const char *input - The input string to split. + * @param char *base - Buffer to store the base part (never contains '_'). + * @param size_t base_len - Size of the base buffer. + * @param char *suffix - Buffer to store the suffix part when valid, else "". + * @param size_t suffix_len - Size of the suffix buffer. + * @return void + */ + +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len) +{ + if (base && base_len > 0) + { + base[0] = '\0'; + } + if (suffix && suffix_len > 0) + { + suffix[0] = '\0'; + } + + if (!input || !base || !suffix) + { + return; + } + + if (base_len == 0 || suffix_len == 0) + { + return; + } + + const char *underscore = strchr(input, '_'); + if (underscore) + { + /* Always split at the first underscore — base never contains '_' */ + size_t b_len = (size_t)(underscore - input); + if (b_len >= base_len) b_len = base_len - 1; + strncpy(base, input, b_len); + base[b_len] = '\0'; + + /* Keep the suffix only when its total length (including '_') is + * within the allowed limit; longer tokens are discarded. */ + if (strlen(underscore) <= RRD_MAX_SUFFIX_LEN) + { + /* Sanitize: retain only [A-Za-z0-9_-] to prevent injection when + * the suffix is later embedded in file names or command arguments. */ + size_t si = 0, di = 0; + size_t max_copy = suffix_len - 1; + while (underscore[si] != '\0' && di < max_copy) + { + char ch = underscore[si]; + if (isalnum((unsigned char)ch) || ch == '_' || ch == '-') + { + suffix[di++] = ch; + } + si++; + } + suffix[di] = '\0'; + } + else + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: Suffix after '%s' exceeds max length (%zu > %d); discarding\n", + __FUNCTION__, __LINE__, base, strlen(underscore), RRD_MAX_SUFFIX_LEN); + suffix[0] = '\0'; + } + } + else + { + /* No underscore — full input is the base */ + strncpy(base, input, base_len - 1); + base[base_len - 1] = '\0'; + suffix[0] = '\0'; + } +} + + /* * @function getParamcount * @brief Calculates the total number of nodes (elements) in the input string, excluding delimiters. @@ -515,7 +613,11 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Memory allocation failed for rfcbuf\n",__FUNCTION__,__LINE__); free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; return; } @@ -535,7 +637,11 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: %s Directory creation failed!!!\n",__FUNCTION__,__LINE__,outdir); free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; return; } else @@ -576,7 +682,26 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf else { RDK_LOG(RDK_LOG_DEBUG,LOG_REMDEBUG,"[%s:%d]: Continue uploading Debug Report for %s from %s... \n",__FUNCTION__,__LINE__,buff->mdata,outdir); - status = uploadDebugoutput(outdir,buff->mdata); + // Use the persisted suffix from buff for upload + char tarName[512] = {0}; + int tar_name_len = 0; + if (buff->suffix && buff->suffix[0] != '\0') + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s%s", buff->mdata, buff->suffix); + } + else + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s", buff->mdata); + } + if ((tar_name_len < 0) || ((size_t)tar_name_len >= sizeof(tarName))) + { + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Failed to build upload file name for %s. snprintf result:%d, buffer size:%zu\n", __FUNCTION__,__LINE__,buff->mdata,tar_name_len,sizeof(tarName)); + status = -1; + } + else + { + status = uploadDebugoutput(outdir, tarName); + } if(status != 0) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: RRD Upload Script Execution Failed!!! status:%d\n",__FUNCTION__,__LINE__,status); @@ -588,14 +713,22 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf } free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; } else { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: No Command excuted as RRD Failed to change directory:%s\n",__FUNCTION__,__LINE__,outdir); free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data + buff->mdata = NULL; free(buff->jsonPath); // free rrd path info + buff->jsonPath = NULL; + free(buff->suffix); // free suffix + buff->suffix = NULL; } } } diff --git a/src/rrdJsonParser.h b/src/rrdJsonParser.h index 299436101..87c4a2622 100644 --- a/src/rrdJsonParser.h +++ b/src/rrdJsonParser.h @@ -47,6 +47,8 @@ issueData* getIssueCommandInfo(issueNodeData *issuestructNode, cJSON *jsoncfg,ch bool processAllDebugCommand(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); bool processAllDeepSleepAwkMetricsCommands(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len); + #ifdef __cplusplus } #endif diff --git a/src/rrdRunCmdThread.c b/src/rrdRunCmdThread.c index ce873d5d5..650144cdb 100644 --- a/src/rrdRunCmdThread.c +++ b/src/rrdRunCmdThread.c @@ -287,7 +287,7 @@ bool executeCommands(issueData *cmdinfo) char pathname[BUF_LEN_256] = {'\0'}; char *outdirpath = NULL; char finalOutFile[BUF_LEN_256] = {'\0'}; - char remoteDebuggerServiceStr[BUF_LEN_256] = {'\0'}; + char remoteDebuggerServiceStr[BUF_LEN_512] = {'\0'}; char *printbuffer = NULL; FILE *filePointer; const char *remoteDebuggerPrefix = "remote_debugger_"; @@ -357,7 +357,7 @@ bool executeCommands(issueData *cmdinfo) strncat(finalOutFile,RRD_OUTPUT_FILE, strlen(RRD_OUTPUT_FILE) + 1); /* Open debug_output.txt file*/ - filePointer = fopen(finalOutFile, "a+"); + filePointer = fopen(finalOutFile, "w+"); if (filePointer == NULL) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Unable to Open File:%s\n",__FUNCTION__,__LINE__,finalOutFile); @@ -376,21 +376,18 @@ bool executeCommands(issueData *cmdinfo) /*Executing Commands using systemd-run*/ RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Executing following commands using systemd-run:\n \"%s\"\n",__FUNCTION__,__LINE__,cmdData->command); - - strncpy(remoteDebuggerServiceStr, remoteDebuggerPrefix, sizeof(remoteDebuggerServiceStr) - 1); - remoteDebuggerServiceStr[sizeof(remoteDebuggerServiceStr) - 1] = '\0'; - strncat(remoteDebuggerServiceStr, cmdData->rfcvalue, sizeof(remoteDebuggerServiceStr) - strlen(remoteDebuggerServiceStr) - 1); - + time_t epochTime = time(NULL); + snprintf(remoteDebuggerServiceStr, sizeof(remoteDebuggerServiceStr),"%s%s%ld", remoteDebuggerPrefix, cmdData->rfcvalue, (long)epochTime); removeQuotes(cmdData->command); FILE *systemdfp = v_secure_popen("r", "systemd-run -r --unit=%s --service-type=oneshot -p RemainAfterExit=yes /bin/sh -c %s", remoteDebuggerServiceStr, cmdData->command); if(systemdfp == NULL) { - RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Starting remote_debugger_%s service failed!!!\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Starting %s service failed!!!\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); } else { - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting remote_debugger_%s service success...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Starting %s service success...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); copyDebugLogDestFile(systemdfp, filePointer); v_secure_pclose(systemdfp); } @@ -400,11 +397,11 @@ bool executeCommands(issueData *cmdinfo) FILE *journalctlfp = v_secure_popen("r", "journalctl -a -u %s", remoteDebuggerServiceStr); if(journalctlfp == NULL) { - RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: journalctl remote_debugger_%s service failed!!!\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: journalctl %s service failed!!!\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); } else { - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: journalctl remote_debugger_%s service success...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: journalctl %s service success...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); copyDebugLogDestFile(journalctlfp, filePointer); v_secure_pclose(journalctlfp); } @@ -417,7 +414,7 @@ bool executeCommands(issueData *cmdinfo) sleep(cmdData->timeout); /*Stop or Reset runtime service for issue*/ - RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Stopping remote_debugger_%s service...\n",__FUNCTION__,__LINE__,cmdData->rfcvalue); + RDK_LOG(RDK_LOG_INFO,LOG_REMDEBUG,"[%s:%d]: Stopping %s service...\n",__FUNCTION__,__LINE__,remoteDebuggerServiceStr); #if !defined(GTEST_ENABLE) v_secure_system("systemctl stop %s", remoteDebuggerServiceStr); free(cmdData->rfcvalue); // free rfcvalue received from RRDEventThreadFunc diff --git a/src/rrdRunCmdThread.h b/src/rrdRunCmdThread.h index a1bd9816a..fb92fb087 100644 --- a/src/rrdRunCmdThread.h +++ b/src/rrdRunCmdThread.h @@ -48,6 +48,7 @@ extern "C" #endif #define RRD_OUTPUT_FILE "debug_outputs.txt" #define BUF_LEN_256 256 +#define BUF_LEN_512 512 /*Public Function*/ void initCache(void); diff --git a/src/rrd_logproc.c b/src/rrd_logproc.c index 30c0cff1c..a33aee466 100644 --- a/src/rrd_logproc.c +++ b/src/rrd_logproc.c @@ -121,7 +121,8 @@ int rrd_logproc_convert_issue_type(const char *input, char *output, size_t size) for (size_t i = 0; input[i] && j < size-1; ++i) { char c = input[i]; if (isalnum((unsigned char)c)) output[j++] = toupper((unsigned char)c); - else if (c == '_' || c == '-' || c == '.') output[j++] = '_'; + else if (c == '_' || c == '.') output[j++] = '_'; + else if (c == '-') output[j++] = '-'; // preserve hyphens so suffix UUID tokens remain distinct // skip other chars } output[j] = 0; diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 7b8c6c837..059e5ce41 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1799,11 +1799,13 @@ TEST_F(RemoveItemTest, HandlesCacheNotNullAndCacheNotEqualsRrdCachecnode) node->mdata = strdup("PkgData"); node->issueString = strdup("IssueString"); node->next = NULL; + node->prev = NULL; cacheDataNode = node; cacheData *node_dummy = (cacheData *)malloc(sizeof(cacheData)); node_dummy->mdata = strdup("PkgData"); node_dummy->issueString = strdup("IssueString"); node_dummy->next = NULL; + node_dummy->prev = NULL; remove_item(node_dummy); EXPECT_NE(cacheDataNode, nullptr); @@ -1863,15 +1865,19 @@ TEST(RemoveSpecialCharacterfromIssueTypeListTest, HandlesStringWithConsecutiveSp /* --------------- Test issueTypeSplitter() from rrdEventProcess --------------- */ TEST(IssueTypeSplitterTest, HandlesStringWithSpecialCharacters) { + /* issueTypeSplitter now performs pure token splitting only; special-character + * removal is done separately by the caller (processIssueTypeEvent) on the + * extracted base, so raw tokens including special chars are returned here. */ char str[] = "a@,b,&,cd,ef"; char **args = NULL; int count = issueTypeSplitter(str, ',', &args); - ASSERT_EQ(count, 4); - ASSERT_STREQ(args[0], "a"); + ASSERT_EQ(count, 5); + ASSERT_STREQ(args[0], "a@"); ASSERT_STREQ(args[1], "b"); - ASSERT_STREQ(args[2], "cd"); - ASSERT_STREQ(args[3], "ef"); + ASSERT_STREQ(args[2], "&"); + ASSERT_STREQ(args[3], "cd"); + ASSERT_STREQ(args[4], "ef"); for (int i = 0; i < count; i++) { @@ -1907,6 +1913,237 @@ TEST(IssueTypeSplitterTest, HandlesEmptyString) free(args); } +/* --------------- Test split_issue_type() from rrdJsonParser --------------- */ +TEST(SplitIssueTypeTest, NoUnderscoreReturnsFull) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, UnderscoreSplitsBaseAndSuffix) +{ + /* Short suffix (total length including '_' is ≤ 9) is accepted and preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab12345", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab12345"); +} + +TEST(SplitIssueTypeTest, MultipleUnderscoresSplitsAtFirst) +{ + /* "abc_def_ghi": suffix "_def_ghi" is 8 chars (≤ 9) → accepted and preserved. + * Only the first '_' is used as the split point; base never contains '_'. */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_def_ghi", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_def_ghi"); +} + +TEST(SplitIssueTypeTest, EmptyInputProducesEmptyOutputs) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullInputDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* Should return without crashing and clear provided outputs to empty strings */ + split_issue_type(NULL, base, sizeof(base), suffix, sizeof(suffix)); + /* NULL input clears the output buffers when buffer pointers are provided */ + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, BaseTruncatedWhenTooSmall) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9) → accepted. + * Base = "abc" (before '_'); with a 4-byte buffer this fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, SuffixTruncatedWhenTooSmall) +{ + /* "abc_12345678": suffix "_12345678" is 9 chars (≤ 9, accepted). Suffix buffer + * is only 5 bytes so suffix is truncated to "_123" + NUL. */ + char base[64] = {0}; + char suffix[5] = {0}; + split_issue_type("abc_12345678", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_123"); + EXPECT_EQ(suffix[sizeof(suffix) - 1], '\0'); + EXPECT_EQ(strlen(suffix), (size_t)(sizeof(suffix) - 1)); +} + +TEST(SplitIssueTypeTest, LeadingUnderscoreGivesEmptyBase) +{ + /* "_suffixonly": split at '_' gives empty base; suffix "_suffixonly" is 11 chars + * (> 9) so it is discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_suffixonly", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullBaseDoesNotCrash) +{ + char suffix[64] = {0}; + /* NULL base pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", NULL, 64, suffix, sizeof(suffix)); + /* suffix remains unchanged when base is NULL */ + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullSuffixDoesNotCrash) +{ + char base[64] = {0}; + /* NULL suffix pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", base, sizeof(base), NULL, 64); + /* base remains unchanged when suffix is NULL */ + EXPECT_STREQ(base, ""); +} + +TEST(SplitIssueTypeTest, ZeroBaseLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* base_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, 0, suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ZeroSuffixLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* suffix_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, sizeof(base), suffix, 0); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ExactFitBase) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9, accepted). + * Base = "abc" (before '_'); 4-byte buffer fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_EQ(base[3], '\0'); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, OnlyUnderscoreInput) +{ + /* "_": split at '_' gives empty base; suffix is "_" (1 char, ≤ 9 → accepted) */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, NineCharSuffixIsAccepted) +{ + /* Suffix of exactly 9 chars (the upper boundary, inclusive) must be accepted */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_12345678", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_12345678"); +} + +TEST(SplitIssueTypeTest, LongSuffixIsDiscarded) +{ + /* Suffix longer than 9 chars is discarded regardless of its content */ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceInfo_1234567890", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceInfo"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixExceedingMaxLengthDiscarded) +{ + /* "_Random-token" is 13 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_Random-token", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixSeventeenCharsDiscarded) +{ + /* "_Search_something" is 17 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_Search_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixTwentyCharsDiscarded) +{ + /* "_LogSearch_something" is 20 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_LogSearch_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixWithUnsafeCharsIsSanitized) +{ + /* "_ab;rm" is 6 chars (≤ 9, accepted) but ';' is unsafe and must be stripped. + * Expected sanitized suffix: "_abrm" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab;rm", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_abrm"); +} + +TEST(SplitIssueTypeTest, SuffixWithOnlyUnsafeCharsBecomesUnderscore) +{ + /* "_!@#" is 4 chars (≤ 9, accepted length-wise) but all payload chars are unsafe. + * After sanitization only the leading '_' remains → suffix="_" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_!@#", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, SuffixHyphensPreserved) +{ + /* "_ab-cd" is 6 chars (≤ 9, accepted) and '-' is in the safe set → preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab-cd", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab-cd"); +} + /* --------------- Test processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -1989,11 +2226,93 @@ TEST(ProcessIssueTypeEvntTest, RBufIsNull){ } TEST(ProcessIssueTypeEvntTest, inDynamic_NoJson){ - data_buf rbuf; + data_buf rbuf = {}; rbuf.mdata = strdup("a"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceTime" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithLogSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceInfo" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceInfo_LogSearch-9abc1def-0000-1111-2222-3333aaaabbbb"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithInvalidSuffixTreatedAsBase) +{ + /* "_Random-token" is 13 chars (> 9): suffix discarded; base = "Device.DeviceTime" */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Random-token"); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, MultipleIssueTypesWithAndWithoutSuffix){ + /* Comma-separated list: one plain type, one with long suffix (> 9, discarded), + * one with another long suffix (> 9, discarded) */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime,Device.DeviceInfo_Search-1234,Device.Net_BadSuffix"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; all entries are processed without leaks */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, WhitespaceOnlyIssueTypeIsSkipped) +{ + /* When the IssueType value from RBUS is whitespace (e.g. a space), + * removeSpecialCharacterfromIssueTypeList() strips it to an empty string. + * processIssueTypeEvent() must detect the post-sanitization empty base + * and skip processing without crashing or invoking processIssueType. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(" "); /* single space — all-special after split */ + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Must not crash and must not reach getIssueInfo with an empty mdata */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, EmptyStringIssueTypeIsSkipped) +{ + /* An empty-string mdata must be handled gracefully — issueTypeSplitter + * returns 1 token that is an empty string, which split_issue_type then + * maps to an empty base, causing the entry to be skipped. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(""); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; } /* ======================== rrdExecuteScript ==============*/ @@ -2117,12 +2436,22 @@ TEST(RRDDataBuffInitTest, InitializeDataBuff) EXPECT_EQ(sbuf.dsEvent, deepSleepEvent); } +TEST(RRDDataBuffInitTest, SuffixInitializedToNull) +{ + /* Verify that the newly added suffix field is initialised to NULL */ + data_buf sbuf; + sbuf.suffix = reinterpret_cast(0xDEADBEEF); /* pre-fill with garbage */ + RRD_data_buff_init(&sbuf, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); + EXPECT_EQ(sbuf.suffix, nullptr); +} + /* --------------- Test RRD_data_buff_deAlloc() from rrdIarm --------------- */ TEST(RRDDataBuffDeAllocTest, DeallocateDataBuff) { data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); sbuf->mdata = (char *)malloc(10 * sizeof(char)); sbuf->jsonPath = (char *)malloc(10 * sizeof(char)); + sbuf->suffix = nullptr; ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } @@ -2134,6 +2463,28 @@ TEST(RRDDataBuffDeAllocTest, NullPointer) ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } +TEST(RRDDataBuffDeAllocTest, DeallocateWithSuffixSet) +{ + /* Verify that suffix is freed without crash when it is non-NULL */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = strdup("IssueType"); + sbuf->jsonPath = nullptr; + sbuf->suffix = strdup("_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + +TEST(RRDDataBuffDeAllocTest, DeallocateWithAllFieldsNull) +{ + /* All pointer fields NULL: should not crash */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = nullptr; + sbuf->jsonPath = nullptr; + sbuf->suffix = nullptr; + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + /* --------------- Test RRD_unsubscribe() from rrdIarm --------------- */ class RRDUnsubscribeTest : public ::testing::Test @@ -3684,6 +4035,7 @@ TEST_F(RRDEventThreadFuncTest, MessageReceiveSuccessEventMsgType) { rbuf.mdata = strdup("Test"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; + rbuf.suffix = strdup("_ab12345"); msgRRDHdr msgHdr; msgHdr.mbody = malloc(sizeof(data_buf)); ASSERT_NE(msgHdr.mbody, nullptr); @@ -4444,12 +4796,22 @@ TEST_F(RRDUploadOrchestrationTest, SpecialCharactersInIssueType) { char sanitized[64]; int result = rrd_logproc_convert_issue_type("test-issue.sub@special!", sanitized, sizeof(sanitized)); EXPECT_EQ(result, 0); - // Should only contain alphanumeric and underscore + // Should only contain alphanumeric, underscore, and hyphen for (const char *p = sanitized; *p; ++p) { - EXPECT_TRUE(isalnum(*p) || *p == '_'); + EXPECT_TRUE(isalnum(*p) || *p == '_' || *p == '-'); } } +// Suffix with hyphens: hyphens must be preserved so portal can parse filename +TEST_F(RRDUploadOrchestrationTest, IssueTypeWithSuffixHyphensPreserved) { + char sanitized[128]; + // Simulates issue type after normalizeIssueName: dots→underscore, hyphens kept + int result = rrd_logproc_convert_issue_type("Device_DeviceIP_Search-67768-67", sanitized, sizeof(sanitized)); + EXPECT_EQ(result, 0); + // Hyphens in suffix must survive so the archive filename delimiter structure is intact + EXPECT_STREQ(sanitized, "DEVICE_DEVICEIP_SEARCH-67768-67"); +} + // Performance test: Large directory TEST_F(RRDUploadOrchestrationTest, LargeDirectoryHandling) { // Create multiple log files From e33765d877abe84ad40ae3b73a8c28443087d8d0 Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Tue, 19 May 2026 23:54:48 +0530 Subject: [PATCH 2/6] Update rrdUnitTestRunner.cpp --- src/unittest/rrdUnitTestRunner.cpp | 1570 +++------------------------- 1 file changed, 126 insertions(+), 1444 deletions(-) diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 059e5ce41..eb277d002 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -84,32 +84,6 @@ #define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" #define GTEST_DEFAULT_RESULT_FILENAME "rdkRemoteDebugger_gtest_report.json" #define GTEST_REPORT_FILEPATH_SIZE 256 -// Define test data directory - use relative path that works from test execution context -#define TEST_DATA_DIR "src/unittest/UTJson/" - -// Helper function to find test files with fallback paths -static const char* find_test_file(const char* filename) { - static char filepath[512]; - const char* search_paths[] = { - "UTJson/", - "src/unittest/UTJson/", - "./UTJson/", - "./src/unittest/UTJson/", - "../src/unittest/UTJson/", - "../../src/unittest/UTJson/", - NULL - }; - - for (int i = 0; search_paths[i] != NULL; i++) { - snprintf(filepath, sizeof(filepath), "%s%s", search_paths[i], filename); - FILE* f = fopen(filepath, "r"); - if (f) { - fclose(f); - return filepath; - } - } - return NULL; // File not found in any path -} using namespace std; using ::testing::_; @@ -951,6 +925,49 @@ TEST(RRDGetProfileStringLengthTest, HandlesIsDeepSleepAwakeEventTrueRRD_DEFAULT_ free(issue.Node); free(issue.subNode); } + +TEST(RRDGetProfileStringLengthTest, HandlesNullStructNode) +{ + int length = RRDGetProfileStringLength(NULL, false); + ASSERT_EQ(length, -1); +} + +TEST(RRDGetProfileStringLengthTest, HandlesNullNodeField) +{ + issueNodeData issue; + issue.Node = NULL; + issue.subNode = strdup("SubNode"); + int length = RRDGetProfileStringLength(&issue, false); + ASSERT_EQ(length, -1); + free(issue.subNode); +} + +TEST(RRDGetProfileStringLengthTest, HandlesNodeLengthExceedsMax) +{ + issueNodeData issue; + issue.Node = (char*)malloc(RRD_DYNAMIC_PROFILE_MAX_LENGTH + 10); + memset(issue.Node, 'A', RRD_DYNAMIC_PROFILE_MAX_LENGTH + 9); + issue.Node[RRD_DYNAMIC_PROFILE_MAX_LENGTH + 9] = '\0'; + issue.subNode = strdup("SubNode"); + int length = RRDGetProfileStringLength(&issue, false); + ASSERT_EQ(length, -1); + free(issue.Node); + free(issue.subNode); +} + +TEST(RRDGetProfileStringLengthTest, HandlesDeepSleepAwakeEventEmptyProfile) +{ + issueNodeData issue; + issue.Node = strdup("MainNode"); + issue.subNode = strdup("SubNode"); + devPropData.deviceType = RRD_DEFAULT_PLTFMS; + // Simulate empty profile name + int (*orig_strlen)(const char*) = strlen; + int length = RRDGetProfileStringLength(&issue, true); + ASSERT_GE(length, 0); // Should not crash + free(issue.Node); + free(issue.subNode); +} #endif /* --------------- Test RRDCheckIssueInDynamicProfile() from rrdDeepSleep --------------- */ class RRDCheckIssueInDynamicProfileTest : public ::testing::Test @@ -1150,6 +1167,82 @@ TEST_F(RRDRdmManagerDownloadRequestTest, DeepSleepAwakeEventIsFalse_SetParamRetu free(buff.mdata); } +TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMSGLengthNegative) +{ + issueNodeData issuestructNode; + issuestructNode.Node = strdup("MainNode"); + issuestructNode.subNode = strdup("SubNode"); + data_buf buff; + buff.mdata = strdup("ValidIssueTypeData"); + buff.jsonPath = strdup("UTJson/validJson.json"); + buff.inDynamic = false; + // Patch RRDGetProfileStringLength to return -1 + // Simulate by passing NULL + RRDRdmManagerDownloadRequest(NULL, buff.jsonPath, &buff, false); + free(issuestructNode.Node); + free(issuestructNode.subNode); + free(buff.jsonPath); + free(buff.mdata); +} + +TEST_F(RRDRdmManagerDownloadRequestTest, HandlesParamStringAllocFail) +{ + // Simulate calloc failure by passing mSGLength=0 (should skip allocation) + issueNodeData issuestructNode; + issuestructNode.Node = strdup("MainNode"); + issuestructNode.subNode = strdup("SubNode"); + data_buf buff; + buff.mdata = strdup("ValidIssueTypeData"); + buff.jsonPath = strdup("UTJson/validJson.json"); + buff.inDynamic = false; + // Directly call with mSGLength=0 by passing NULL node + RRDRdmManagerDownloadRequest(NULL, buff.jsonPath, &buff, false); + free(issuestructNode.Node); + free(issuestructNode.subNode); + free(buff.jsonPath); + free(buff.mdata); +} + +TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMsgDataStringAllocFail) +{ + // Simulate msgDataString allocation fail by passing a very large node name + issueNodeData issuestructNode; + issuestructNode.Node = (char*)malloc(1024*1024); + memset(issuestructNode.Node, 'A', 1024*1024-1); + issuestructNode.Node[1024*1024-1] = '\0'; + issuestructNode.subNode = strdup("SubNode"); + data_buf buff; + buff.mdata = strdup("ValidIssueTypeData"); + buff.jsonPath = strdup("UTJson/validJson.json"); + buff.inDynamic = false; + // This will likely fail to allocate msgDataString + RRDRdmManagerDownloadRequest(&issuestructNode, buff.jsonPath, &buff, false); + free(issuestructNode.Node); + free(issuestructNode.subNode); + free(buff.jsonPath); + free(buff.mdata); +} + +TEST_F(RRDRdmManagerDownloadRequestTest, HandlesAppendModeTrue) +{ + issueNodeData issuestructNode; + issuestructNode.Node = strdup("MainNode"); + issuestructNode.subNode = strdup("SubNode"); + data_buf buff; + buff.mdata = strdup("ValidIssueTypeData"); + buff.jsonPath = strdup("UTJson/validJson.json"); + buff.inDynamic = false; + buff.appendMode = true; + EXPECT_CALL(mock_rbus_api, rbusValue_Init(_)).WillOnce(Return(RBUS_ERROR_SUCCESS)); + EXPECT_CALL(mock_rbus_api, rbusValue_SetString(_, _)).WillOnce(Return(RBUS_ERROR_SUCCESS)); + EXPECT_CALL(mock_rbus_api, rbus_set(_, _, _, _)).WillOnce(Return(RBUS_ERROR_SUCCESS)); + RRDRdmManagerDownloadRequest(&issuestructNode, buff.jsonPath, &buff, false); + free(issuestructNode.Node); + free(issuestructNode.subNode); + free(buff.jsonPath); + free(buff.mdata); +} + /* --------------- Test RRDProcessDeepSleepAwakeEvents() from rrdDeepSleep --------------- */ class RRDProcessDeepSleepAwakeEventsTest : public ::testing::Test { @@ -1799,13 +1892,11 @@ TEST_F(RemoveItemTest, HandlesCacheNotNullAndCacheNotEqualsRrdCachecnode) node->mdata = strdup("PkgData"); node->issueString = strdup("IssueString"); node->next = NULL; - node->prev = NULL; cacheDataNode = node; cacheData *node_dummy = (cacheData *)malloc(sizeof(cacheData)); node_dummy->mdata = strdup("PkgData"); node_dummy->issueString = strdup("IssueString"); node_dummy->next = NULL; - node_dummy->prev = NULL; remove_item(node_dummy); EXPECT_NE(cacheDataNode, nullptr); @@ -1865,19 +1956,15 @@ TEST(RemoveSpecialCharacterfromIssueTypeListTest, HandlesStringWithConsecutiveSp /* --------------- Test issueTypeSplitter() from rrdEventProcess --------------- */ TEST(IssueTypeSplitterTest, HandlesStringWithSpecialCharacters) { - /* issueTypeSplitter now performs pure token splitting only; special-character - * removal is done separately by the caller (processIssueTypeEvent) on the - * extracted base, so raw tokens including special chars are returned here. */ char str[] = "a@,b,&,cd,ef"; char **args = NULL; int count = issueTypeSplitter(str, ',', &args); - ASSERT_EQ(count, 5); - ASSERT_STREQ(args[0], "a@"); + ASSERT_EQ(count, 4); + ASSERT_STREQ(args[0], "a"); ASSERT_STREQ(args[1], "b"); - ASSERT_STREQ(args[2], "&"); - ASSERT_STREQ(args[3], "cd"); - ASSERT_STREQ(args[4], "ef"); + ASSERT_STREQ(args[2], "cd"); + ASSERT_STREQ(args[3], "ef"); for (int i = 0; i < count; i++) { @@ -1913,237 +2000,6 @@ TEST(IssueTypeSplitterTest, HandlesEmptyString) free(args); } -/* --------------- Test split_issue_type() from rrdJsonParser --------------- */ -TEST(SplitIssueTypeTest, NoUnderscoreReturnsFull) -{ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("Device.DeviceTime", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceTime"); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, UnderscoreSplitsBaseAndSuffix) -{ - /* Short suffix (total length including '_' is ≤ 9) is accepted and preserved */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("Device.DeviceTime_ab12345", - base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceTime"); - EXPECT_STREQ(suffix, "_ab12345"); -} - -TEST(SplitIssueTypeTest, MultipleUnderscoresSplitsAtFirst) -{ - /* "abc_def_ghi": suffix "_def_ghi" is 8 chars (≤ 9) → accepted and preserved. - * Only the first '_' is used as the split point; base never contains '_'. */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("abc_def_ghi", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_STREQ(suffix, "_def_ghi"); -} - -TEST(SplitIssueTypeTest, EmptyInputProducesEmptyOutputs) -{ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, ""); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, NullInputDoesNotCrash) -{ - char base[64] = {0}; - char suffix[64] = {0}; - /* Should return without crashing and clear provided outputs to empty strings */ - split_issue_type(NULL, base, sizeof(base), suffix, sizeof(suffix)); - /* NULL input clears the output buffers when buffer pointers are provided */ - EXPECT_STREQ(base, ""); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, BaseTruncatedWhenTooSmall) -{ - /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9) → accepted. - * Base = "abc" (before '_'); with a 4-byte buffer this fits exactly. */ - char base[4] = {0}; - char suffix[64] = {0}; - split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_STREQ(suffix, "_suffix"); -} - -TEST(SplitIssueTypeTest, SuffixTruncatedWhenTooSmall) -{ - /* "abc_12345678": suffix "_12345678" is 9 chars (≤ 9, accepted). Suffix buffer - * is only 5 bytes so suffix is truncated to "_123" + NUL. */ - char base[64] = {0}; - char suffix[5] = {0}; - split_issue_type("abc_12345678", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_STREQ(suffix, "_123"); - EXPECT_EQ(suffix[sizeof(suffix) - 1], '\0'); - EXPECT_EQ(strlen(suffix), (size_t)(sizeof(suffix) - 1)); -} - -TEST(SplitIssueTypeTest, LeadingUnderscoreGivesEmptyBase) -{ - /* "_suffixonly": split at '_' gives empty base; suffix "_suffixonly" is 11 chars - * (> 9) so it is discarded */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("_suffixonly", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, ""); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, NullBaseDoesNotCrash) -{ - char suffix[64] = {0}; - /* NULL base pointer: should return without crashing */ - split_issue_type("Device.DeviceTime_Search", NULL, 64, suffix, sizeof(suffix)); - /* suffix remains unchanged when base is NULL */ - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, NullSuffixDoesNotCrash) -{ - char base[64] = {0}; - /* NULL suffix pointer: should return without crashing */ - split_issue_type("Device.DeviceTime_Search", base, sizeof(base), NULL, 64); - /* base remains unchanged when suffix is NULL */ - EXPECT_STREQ(base, ""); -} - -TEST(SplitIssueTypeTest, ZeroBaseLenDoesNotCrash) -{ - char base[64] = {0}; - char suffix[64] = {0}; - /* base_len == 0: should return without writing anything */ - split_issue_type("abc_def", base, 0, suffix, sizeof(suffix)); - EXPECT_STREQ(base, ""); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, ZeroSuffixLenDoesNotCrash) -{ - char base[64] = {0}; - char suffix[64] = {0}; - /* suffix_len == 0: should return without writing anything */ - split_issue_type("abc_def", base, sizeof(base), suffix, 0); - EXPECT_STREQ(base, ""); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, ExactFitBase) -{ - /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9, accepted). - * Base = "abc" (before '_'); 4-byte buffer fits exactly. */ - char base[4] = {0}; - char suffix[64] = {0}; - split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_EQ(base[3], '\0'); - EXPECT_STREQ(suffix, "_suffix"); -} - -TEST(SplitIssueTypeTest, OnlyUnderscoreInput) -{ - /* "_": split at '_' gives empty base; suffix is "_" (1 char, ≤ 9 → accepted) */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("_", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, ""); - EXPECT_STREQ(suffix, "_"); -} - -TEST(SplitIssueTypeTest, NineCharSuffixIsAccepted) -{ - /* Suffix of exactly 9 chars (the upper boundary, inclusive) must be accepted */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("Device.DeviceTime_12345678", - base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceTime"); - EXPECT_STREQ(suffix, "_12345678"); -} - -TEST(SplitIssueTypeTest, LongSuffixIsDiscarded) -{ - /* Suffix longer than 9 chars is discarded regardless of its content */ - char base[64] = {0}; - char suffix[128] = {0}; - split_issue_type("Device.DeviceInfo_1234567890", - base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceInfo"); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, SuffixExceedingMaxLengthDiscarded) -{ - /* "_Random-token" is 13 chars (> 9) → suffix discarded */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("Device.DeviceTime_Random-token", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceTime"); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, SuffixSeventeenCharsDiscarded) -{ - /* "_Search_something" is 17 chars (> 9) → suffix discarded */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("abc_Search_something", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, SuffixTwentyCharsDiscarded) -{ - /* "_LogSearch_something" is 20 chars (> 9) → suffix discarded */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("abc_LogSearch_something", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_STREQ(suffix, ""); -} - -TEST(SplitIssueTypeTest, SuffixWithUnsafeCharsIsSanitized) -{ - /* "_ab;rm" is 6 chars (≤ 9, accepted) but ';' is unsafe and must be stripped. - * Expected sanitized suffix: "_abrm" */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("Device.DeviceTime_ab;rm", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceTime"); - EXPECT_STREQ(suffix, "_abrm"); -} - -TEST(SplitIssueTypeTest, SuffixWithOnlyUnsafeCharsBecomesUnderscore) -{ - /* "_!@#" is 4 chars (≤ 9, accepted length-wise) but all payload chars are unsafe. - * After sanitization only the leading '_' remains → suffix="_" */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("abc_!@#", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "abc"); - EXPECT_STREQ(suffix, "_"); -} - -TEST(SplitIssueTypeTest, SuffixHyphensPreserved) -{ - /* "_ab-cd" is 6 chars (≤ 9, accepted) and '-' is in the safe set → preserved */ - char base[64] = {0}; - char suffix[64] = {0}; - split_issue_type("Device.DeviceTime_ab-cd", base, sizeof(base), suffix, sizeof(suffix)); - EXPECT_STREQ(base, "Device.DeviceTime"); - EXPECT_STREQ(suffix, "_ab-cd"); -} - /* --------------- Test processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -2226,93 +2082,11 @@ TEST(ProcessIssueTypeEvntTest, RBufIsNull){ } TEST(ProcessIssueTypeEvntTest, inDynamic_NoJson){ - data_buf rbuf = {}; + data_buf rbuf; rbuf.mdata = strdup("a"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; -} - -TEST(ProcessIssueTypeEvntTest, IssueTypeWithSearchSuffix_inDynamic_NoJson){ - /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceTime" is used */ - data_buf rbuf = {}; - rbuf.mdata = strdup("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); - rbuf.inDynamic = true; - rbuf.jsonPath = nullptr; - /* Should not crash; long suffix is discarded, base is processed normally */ - processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; -} - -TEST(ProcessIssueTypeEvntTest, IssueTypeWithLogSearchSuffix_inDynamic_NoJson){ - /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceInfo" is used */ - data_buf rbuf = {}; - rbuf.mdata = strdup("Device.DeviceInfo_LogSearch-9abc1def-0000-1111-2222-3333aaaabbbb"); - rbuf.inDynamic = true; - rbuf.jsonPath = nullptr; - /* Should not crash; long suffix is discarded, base is processed normally */ - processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; -} - -TEST(ProcessIssueTypeEvntTest, IssueTypeWithInvalidSuffixTreatedAsBase) -{ - /* "_Random-token" is 13 chars (> 9): suffix discarded; base = "Device.DeviceTime" */ - data_buf rbuf = {}; - rbuf.mdata = strdup("Device.DeviceTime_Random-token"); - rbuf.inDynamic = false; - rbuf.jsonPath = nullptr; - /* Should not crash; long suffix is discarded, base is processed normally */ - processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; -} - -TEST(ProcessIssueTypeEvntTest, MultipleIssueTypesWithAndWithoutSuffix){ - /* Comma-separated list: one plain type, one with long suffix (> 9, discarded), - * one with another long suffix (> 9, discarded) */ - data_buf rbuf = {}; - rbuf.mdata = strdup("Device.DeviceTime,Device.DeviceInfo_Search-1234,Device.Net_BadSuffix"); - rbuf.inDynamic = true; - rbuf.jsonPath = nullptr; - /* Should not crash; all entries are processed without leaks */ - processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; -} - -TEST(ProcessIssueTypeEvntTest, WhitespaceOnlyIssueTypeIsSkipped) -{ - /* When the IssueType value from RBUS is whitespace (e.g. a space), - * removeSpecialCharacterfromIssueTypeList() strips it to an empty string. - * processIssueTypeEvent() must detect the post-sanitization empty base - * and skip processing without crashing or invoking processIssueType. */ - data_buf rbuf = {}; - rbuf.mdata = strdup(" "); /* single space — all-special after split */ - rbuf.inDynamic = false; - rbuf.jsonPath = nullptr; - /* Must not crash and must not reach getIssueInfo with an empty mdata */ - processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; -} - -TEST(ProcessIssueTypeEvntTest, EmptyStringIssueTypeIsSkipped) -{ - /* An empty-string mdata must be handled gracefully — issueTypeSplitter - * returns 1 token that is an empty string, which split_issue_type then - * maps to an empty base, causing the entry to be skipped. */ - data_buf rbuf = {}; - rbuf.mdata = strdup(""); - rbuf.inDynamic = false; - rbuf.jsonPath = nullptr; - processIssueTypeEvent(&rbuf); - free(rbuf.mdata); - rbuf.mdata = NULL; } /* ======================== rrdExecuteScript ==============*/ @@ -2436,22 +2210,12 @@ TEST(RRDDataBuffInitTest, InitializeDataBuff) EXPECT_EQ(sbuf.dsEvent, deepSleepEvent); } -TEST(RRDDataBuffInitTest, SuffixInitializedToNull) -{ - /* Verify that the newly added suffix field is initialised to NULL */ - data_buf sbuf; - sbuf.suffix = reinterpret_cast(0xDEADBEEF); /* pre-fill with garbage */ - RRD_data_buff_init(&sbuf, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); - EXPECT_EQ(sbuf.suffix, nullptr); -} - /* --------------- Test RRD_data_buff_deAlloc() from rrdIarm --------------- */ TEST(RRDDataBuffDeAllocTest, DeallocateDataBuff) { data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); sbuf->mdata = (char *)malloc(10 * sizeof(char)); sbuf->jsonPath = (char *)malloc(10 * sizeof(char)); - sbuf->suffix = nullptr; ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } @@ -2463,28 +2227,6 @@ TEST(RRDDataBuffDeAllocTest, NullPointer) ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } -TEST(RRDDataBuffDeAllocTest, DeallocateWithSuffixSet) -{ - /* Verify that suffix is freed without crash when it is non-NULL */ - data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); - sbuf->mdata = strdup("IssueType"); - sbuf->jsonPath = nullptr; - sbuf->suffix = strdup("_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); - - ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); -} - -TEST(RRDDataBuffDeAllocTest, DeallocateWithAllFieldsNull) -{ - /* All pointer fields NULL: should not crash */ - data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); - sbuf->mdata = nullptr; - sbuf->jsonPath = nullptr; - sbuf->suffix = nullptr; - - ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); -} - /* --------------- Test RRD_unsubscribe() from rrdIarm --------------- */ class RRDUnsubscribeTest : public ::testing::Test @@ -4035,7 +3777,6 @@ TEST_F(RRDEventThreadFuncTest, MessageReceiveSuccessEventMsgType) { rbuf.mdata = strdup("Test"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; - rbuf.suffix = strdup("_ab12345"); msgRRDHdr msgHdr; msgHdr.mbody = malloc(sizeof(data_buf)); ASSERT_NE(msgHdr.mbody, nullptr); @@ -4796,22 +4537,12 @@ TEST_F(RRDUploadOrchestrationTest, SpecialCharactersInIssueType) { char sanitized[64]; int result = rrd_logproc_convert_issue_type("test-issue.sub@special!", sanitized, sizeof(sanitized)); EXPECT_EQ(result, 0); - // Should only contain alphanumeric, underscore, and hyphen + // Should only contain alphanumeric and underscore for (const char *p = sanitized; *p; ++p) { - EXPECT_TRUE(isalnum(*p) || *p == '_' || *p == '-'); + EXPECT_TRUE(isalnum(*p) || *p == '_'); } } -// Suffix with hyphens: hyphens must be preserved so portal can parse filename -TEST_F(RRDUploadOrchestrationTest, IssueTypeWithSuffixHyphensPreserved) { - char sanitized[128]; - // Simulates issue type after normalizeIssueName: dots→underscore, hyphens kept - int result = rrd_logproc_convert_issue_type("Device_DeviceIP_Search-67768-67", sanitized, sizeof(sanitized)); - EXPECT_EQ(result, 0); - // Hyphens in suffix must survive so the archive filename delimiter structure is intact - EXPECT_STREQ(sanitized, "DEVICE_DEVICEIP_SEARCH-67768-67"); -} - // Performance test: Large directory TEST_F(RRDUploadOrchestrationTest, LargeDirectoryHandling) { // Create multiple log files @@ -5129,1055 +4860,6 @@ TEST_F(RRDUploadOrchestrationTest, PriorityAdjustment) { } -/* ====================== Profile Management Function Tests ================*/ -/* --------------- Test load_profile_category() from rrdInterface --------------- */ -class LoadProfileCategoryTest : public ::testing::Test -{ -protected: - void SetUp() override - { - // Clean up any existing test file - remove(RRD_PROFILE_CATEGORY_FILE); - - // Reset global category - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - } - - void TearDown() override - { - // Clean up test file - remove(RRD_PROFILE_CATEGORY_FILE); - - // Reset global category - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - } -}; - -TEST_F(LoadProfileCategoryTest, LoadFromExistingFile) -{ - // Create test file with category - FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w"); - ASSERT_NE(fp, nullptr); - fprintf(fp, "Video\n"); - fclose(fp); - - int result = load_profile_category(); - EXPECT_EQ(result, 0); - EXPECT_STREQ(RRDProfileCategory, "Video"); -} - -TEST_F(LoadProfileCategoryTest, LoadFromNonExistentFile) -{ - int result = load_profile_category(); - EXPECT_EQ(result, -1); - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -TEST_F(LoadProfileCategoryTest, LoadFromEmptyFile) -{ - // Create empty file - FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w"); - ASSERT_NE(fp, nullptr); - fclose(fp); - - int result = load_profile_category(); - EXPECT_EQ(result, -1); - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -TEST_F(LoadProfileCategoryTest, LoadWithNewlineHandling) -{ - // Create test file with multiple lines - FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w"); - ASSERT_NE(fp, nullptr); - fprintf(fp, "Network\nextra line"); - fclose(fp); - - int result = load_profile_category(); - EXPECT_EQ(result, 0); - EXPECT_STREQ(RRDProfileCategory, "Network"); -} - -/* --------------- Test save_profile_category() from rrdInterface --------------- */ -class SaveProfileCategoryTest : public ::testing::Test -{ -protected: - void SetUp() override - { - // Clean up any existing test file - remove(RRD_PROFILE_CATEGORY_FILE); - - // Set test category - strncpy(RRDProfileCategory, "Audio", sizeof(RRDProfileCategory) - 1); - RRDProfileCategory[sizeof(RRDProfileCategory) - 1] = '\0'; - } - - void TearDown() override - { - // Clean up test file - remove(RRD_PROFILE_CATEGORY_FILE); - - // Reset global category - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - } -}; - -TEST_F(SaveProfileCategoryTest, SaveToFile) -{ - int result = save_profile_category(); - EXPECT_EQ(result, 0); - - // Verify file was created and contains correct content - FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "r"); - ASSERT_NE(fp, nullptr); - - char buffer[256]; - ASSERT_NE(fgets(buffer, sizeof(buffer), fp), nullptr); - fclose(fp); - - // Remove newline for comparison - char *newline = strchr(buffer, '\n'); - if (newline) *newline = '\0'; - - EXPECT_STREQ(buffer, "Audio"); -} - -TEST_F(SaveProfileCategoryTest, SaveToReadOnlyDirectory) -{ - // This test checks behavior when file cannot be written - // Create a scenario where the directory might not be writable - // The function should return -1 in error cases - - // We can't easily test read-only scenarios in unit tests, - // but we can verify the function handles file creation properly - int result = save_profile_category(); - - // Should succeed in normal test environment - EXPECT_GE(result, -1); // Either success (0) or expected failure (-1) -} - -/* --------------- Test has_direct_commands() from rrdInterface --------------- */ -class HasDirectCommandsTest : public ::testing::Test -{ -protected: - cJSON *category; - - void SetUp() override - { - category = nullptr; - } - - void TearDown() override - { - if (category) { - cJSON_Delete(category); - } - } -}; - -TEST_F(HasDirectCommandsTest, CategoryWithDirectCommands) -{ - // Create category with direct commands structure - const char *json_str = R"({ - "IssueType1": { - "Commands": "ls -la" - }, - "IssueType2": { - "Commands": "ps aux" - } - })"; - - category = cJSON_Parse(json_str); - ASSERT_NE(category, nullptr); - - bool result = has_direct_commands(category); - EXPECT_TRUE(result); -} - -TEST_F(HasDirectCommandsTest, CategoryWithoutDirectCommands) -{ - // Create category without Commands field - const char *json_str = R"({ - "IssueType1": { - "Description": "Test issue" - }, - "IssueType2": { - "Timeout": 30 - } - })"; - - category = cJSON_Parse(json_str); - ASSERT_NE(category, nullptr); - - bool result = has_direct_commands(category); - EXPECT_FALSE(result); -} - -TEST_F(HasDirectCommandsTest, EmptyCategory) -{ - category = cJSON_CreateObject(); - ASSERT_NE(category, nullptr); - - bool result = has_direct_commands(category); - EXPECT_FALSE(result); -} - -TEST_F(HasDirectCommandsTest, NullCategory) -{ - bool result = has_direct_commands(nullptr); - EXPECT_FALSE(result); -} - -/* --------------- Test read_profile_json_file() from rrdInterface --------------- */ -class ReadProfileJsonFileTest : public ::testing::Test -{ -protected: - const char *test_file = "/tmp/test_profile.json"; - long file_size; - - void SetUp() override - { - file_size = 0; - } - - void TearDown() override - { - remove(test_file); - } -}; - -TEST_F(ReadProfileJsonFileTest, ReadValidFile) -{ - // Create test file with JSON content - const char *json_content = R"({"Video": {"issue1": {"Commands": "test"}}})"; - FILE *fp = fopen(test_file, "w"); - ASSERT_NE(fp, nullptr); - fprintf(fp, "%s", json_content); - fclose(fp); - - char *result = read_profile_json_file(test_file, &file_size); - ASSERT_NE(result, nullptr); - EXPECT_GT(file_size, 0); - EXPECT_STREQ(result, json_content); - - free(result); -} - -TEST_F(ReadProfileJsonFileTest, ReadNonExistentFile) -{ - char *result = read_profile_json_file("/tmp/nonexistent.json", &file_size); - EXPECT_EQ(result, nullptr); - EXPECT_EQ(file_size, 0); -} - -TEST_F(ReadProfileJsonFileTest, ReadEmptyFile) -{ - // Create empty file - FILE *fp = fopen(test_file, "w"); - ASSERT_NE(fp, nullptr); - fclose(fp); - - char *result = read_profile_json_file(test_file, &file_size); - EXPECT_EQ(result, nullptr); -} - -TEST_F(ReadProfileJsonFileTest, ReadNullFilename) -{ - char *result = read_profile_json_file(nullptr, &file_size); - EXPECT_EQ(result, nullptr); -} - -/* --------------- Test get_all_categories_json() from rrdInterface --------------- */ -class GetAllCategoriesJsonTest : public ::testing::Test -{ -protected: - cJSON *json; - - void SetUp() override - { - json = nullptr; - } - - void TearDown() override - { - if (json) { - cJSON_Delete(json); - } - } -}; - -TEST_F(GetAllCategoriesJsonTest, GetAllValidCategories) -{ - // Create JSON with multiple categories - const char *json_str = R"({ - "Video": { - "issue1": {"Commands": "test1"}, - "issue2": {"Commands": "test2"} - }, - "Audio": { - "issue3": {"Commands": "test3"} - } - })"; - - json = cJSON_Parse(json_str); - ASSERT_NE(json, nullptr); - - char *result = get_all_categories_json(json); - ASSERT_NE(result, nullptr); - - // Parse result to verify structure - cJSON *result_json = cJSON_Parse(result); - ASSERT_NE(result_json, nullptr); - - // Check that Video and Audio categories exist - cJSON *video = cJSON_GetObjectItem(result_json, "Video"); - cJSON *audio = cJSON_GetObjectItem(result_json, "Audio"); - - EXPECT_NE(video, nullptr); - EXPECT_NE(audio, nullptr); - EXPECT_TRUE(cJSON_IsArray(video)); - EXPECT_TRUE(cJSON_IsArray(audio)); - - cJSON_Delete(result_json); - free(result); -} - -TEST_F(GetAllCategoriesJsonTest, GetAllFromEmptyJson) -{ - json = cJSON_CreateObject(); - ASSERT_NE(json, nullptr); - - char *result = get_all_categories_json(json); - ASSERT_NE(result, nullptr); - - // Should return empty object - cJSON *result_json = cJSON_Parse(result); - ASSERT_NE(result_json, nullptr); - EXPECT_EQ(cJSON_GetArraySize(result_json), 0); - - cJSON_Delete(result_json); - free(result); -} - -TEST_F(GetAllCategoriesJsonTest, GetAllFromNullJson) -{ - char *result = get_all_categories_json(nullptr); - ASSERT_NE(result, nullptr); - - // NULL input should return an empty JSON object - cJSON *result_json = cJSON_Parse(result); - ASSERT_NE(result_json, nullptr); - EXPECT_EQ(cJSON_GetArraySize(result_json), 0); - - cJSON_Delete(result_json); - free(result); -} - -/* --------------- Test get_specific_category_json() from rrdInterface --------------- */ -class GetSpecificCategoryJsonTest : public ::testing::Test -{ -protected: - cJSON *json; - - void SetUp() override - { - json = nullptr; - } - - void TearDown() override - { - if (json) { - cJSON_Delete(json); - } - } -}; - -TEST_F(GetSpecificCategoryJsonTest, GetExistingCategory) -{ - const char *json_str = R"({ - "Video": { - "issue1": {"Commands": "test1"}, - "issue2": {"Commands": "test2"} - }, - "Audio": { - "issue3": {"Commands": "test3"} - } - })"; - - json = cJSON_Parse(json_str); - ASSERT_NE(json, nullptr); - - char *result = get_specific_category_json(json, "Video"); - ASSERT_NE(result, nullptr); - - // Parse result to verify it's an array with Video issues - cJSON *result_json = cJSON_Parse(result); - ASSERT_NE(result_json, nullptr); - EXPECT_TRUE(cJSON_IsArray(result_json)); - - cJSON_Delete(result_json); - free(result); -} - -TEST_F(GetSpecificCategoryJsonTest, GetNonExistentCategory) -{ - const char *json_str = R"({ - "Video": { - "issue1": {"Commands": "test1"} - } - })"; - - json = cJSON_Parse(json_str); - ASSERT_NE(json, nullptr); - - char *result = get_specific_category_json(json, "NonExistent"); - ASSERT_NE(result, nullptr); - - cJSON *result_json = cJSON_Parse(result); - ASSERT_NE(result_json, nullptr); - EXPECT_FALSE(cJSON_IsArray(result_json)); - EXPECT_EQ(cJSON_GetArraySize(result_json), 1); - - cJSON_Delete(result_json); - free(result); -} - -TEST_F(GetSpecificCategoryJsonTest, GetFromNullJson) -{ - char *result = get_specific_category_json(nullptr, "Video"); - // Should handle null input gracefully - ASSERT_NE(result, nullptr); - - cJSON *result_json = cJSON_Parse(result); - ASSERT_NE(result_json, nullptr); - EXPECT_FALSE(cJSON_IsArray(result_json)); - - cJSON_Delete(result_json); - free(result); -} - -/* --------------- Test rrd_SetHandler() from rrdInterface --------------- */ -class RrdSetHandlerTest : public ::testing::Test -{ -protected: - MockRBusApi mock_rbus_api; - - void SetUp() override - { - // Clear any existing profile category - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - - // Clean up test file - remove(RRD_PROFILE_CATEGORY_FILE); - } - - void TearDown() override - { - // Clean up - remove(RRD_PROFILE_CATEGORY_FILE); - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - } -}; - -TEST_F(RrdSetHandlerTest, SetValidProfileCategory) -{ - // This test verifies the overall logic without deep RBUS API testing - // since those are mocked and complex to set up properly - - // Set a test category directly to verify save/load workflow - strncpy(RRDProfileCategory, "TestCategory", sizeof(RRDProfileCategory) - 1); - - // Test save functionality - int save_result = save_profile_category(); - EXPECT_EQ(save_result, 0); - - // Clear and reload to verify - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - int load_result = load_profile_category(); - EXPECT_EQ(load_result, 0); - EXPECT_STREQ(RRDProfileCategory, "TestCategory"); -} - -/* --------------- Test rrd_GetHandler() from rrdInterface --------------- */ -class RrdGetHandlerTest : public ::testing::Test -{ -protected: - const char *test_json_file = "/tmp/test_profile.json"; - - void SetUp() override - { - // Create test JSON file - const char *json_content = R"({ - "Video": { - "issue1": {"Commands": "test1"}, - "issue2": {"Commands": "test2"} - }, - "Audio": { - "issue3": {"Commands": "test3"} - } - })"; - - FILE *fp = fopen(test_json_file, "w"); - if (fp) { - fprintf(fp, "%s", json_content); - fclose(fp); - } - - // Set up profile category - strncpy(RRDProfileCategory, "all", sizeof(RRDProfileCategory) - 1); - } - - void TearDown() override - { - remove(test_json_file); - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - } -}; - -TEST_F(RrdGetHandlerTest, TestProfileDataProcessing) -{ - // Test the helper functions used by rrd_GetHandler - - long file_size; - char *json_buffer = read_profile_json_file(test_json_file, &file_size); - ASSERT_NE(json_buffer, nullptr); - EXPECT_GT(file_size, 0); - - cJSON *json = cJSON_Parse(json_buffer); - ASSERT_NE(json, nullptr); - - // Test get_all_categories_json - char *all_result = get_all_categories_json(json); - ASSERT_NE(all_result, nullptr); - - // Test get_specific_category_json - char *specific_result = get_specific_category_json(json, "Video"); - ASSERT_NE(specific_result, nullptr); - - // Cleanup - cJSON_Delete(json); - free(json_buffer); - free(all_result); - free(specific_result); -} - -/* --------------- Test set_rbus_response() from rrdInterface --------------- */ -class SetRbusResponseTest : public ::testing::Test -{ -protected: - MockRBusApi mock_rbus_api; - - void SetUp() override - { - // Note: This is a complex function to test due to RBUS dependencies - // These tests verify the basic logic flow - } - - void TearDown() override - { - // Cleanup if needed - } -}; - -TEST_F(SetRbusResponseTest, HandlesNullJsonString) -{ - // Test with null JSON string - should return error - rbusError_t result = set_rbus_response(nullptr, nullptr); - EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); -} - -TEST_F(SetRbusResponseTest, HandlesValidJsonString) -{ - // This is difficult to test without full RBUS mock setup - // The function should succeed with valid inputs in a real environment - const char *test_json = R"({"test": "data"})"; - - // Without full RBUS setup, we can't fully test this - // But we can verify it handles the null case properly - rbusError_t result = set_rbus_response(nullptr, test_json); - // Expected behavior depends on RBUS implementation details -} - -/* ====================== rrd_SetHandler and rrd_GetHandler ================*/ - -// Simple mock for RBUS profile handler tests -class RBusProfileMock { -public: - std::string mockPropertyName; - std::string mockPropertyValue; - rbusValueType_t mockValueType = RBUS_STRING; - std::string mockResponseValue; -}; - -// Global mock RBUS property for profile handler tests -struct MockRBusProperty { - std::string name; - std::string value; - rbusValueType_t type; -} g_mockRbusProperty; - -// Mock RBUS function implementations for profile handler tests -static char const* mock_rbusProperty_GetName(rbusProperty_t property) { - (void)property; - return g_mockRbusProperty.name.c_str(); -} - -static rbusValue_t mock_rbusProperty_GetValue(rbusProperty_t property) { - (void)property; - return (rbusValue_t)g_mockRbusProperty.value.c_str(); -} - -static rbusValueType_t mock_rbusValue_GetType(rbusValue_t value) { - (void)value; - return g_mockRbusProperty.type; -} - -static char const* mock_rbusValue_GetString(rbusValue_t value, int* len) { - (void)value; - if (len) *len = g_mockRbusProperty.value.length(); - return g_mockRbusProperty.value.c_str(); -} - -static void mock_rbusProperty_SetValue(rbusProperty_t property, rbusValue_t value) { - (void)property; (void)value; -} - -static void mock_rbusValue_Release(rbusValue_t value) { - (void)value; -} - -// External declarations for function pointers from Client_Mock.cpp -extern char const* (*rbusProperty_GetName)(rbusProperty_t); -extern rbusValue_t (*rbusProperty_GetValue)(rbusProperty_t); -extern rbusValueType_t (*rbusValue_GetType)(rbusValue_t); -extern char const* (*rbusValue_GetString)(rbusValue_t, int*); -extern void (*rbusProperty_SetValue)(rbusProperty_t, rbusValue_t); -extern void (*rbusValue_Release)(rbusValue_t); - -// Test fixture for RRD Profile Handler tests -class RRDProfileHandlerTest : public ::testing::Test { -protected: - RBusProfileMock mockRBusApi; - MockRBusApi mockWrapper; // Add mock for RBusApiWrapper - - // Store original function pointers - char const* (*orig_rbusProperty_GetName)(rbusProperty_t); - rbusValue_t (*orig_rbusProperty_GetValue)(rbusProperty_t); - rbusValueType_t (*orig_rbusValue_GetType)(rbusValue_t); - char const* (*orig_rbusValue_GetString)(rbusValue_t, int*); - void (*orig_rbusProperty_SetValue)(rbusProperty_t, rbusValue_t); - void (*orig_rbusValue_Release)(rbusValue_t); - - void SetUp() override { - // Reset global state - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - strcpy(RRDProfileCategory, "all"); - - // Reset mock RBUS data - mockRBusApi.mockPropertyName.clear(); - mockRBusApi.mockPropertyValue.clear(); - mockRBusApi.mockValueType = RBUS_STRING; - mockRBusApi.mockResponseValue.clear(); - - // Reset global mock property - g_mockRbusProperty.name.clear(); - g_mockRbusProperty.value.clear(); - g_mockRbusProperty.type = RBUS_STRING; - - // Clear any existing RBusApiWrapper implementation first - RBusApiWrapper::clearImpl(); - - // Set up RBusApiWrapper with mock implementation - RBusApiWrapper::setImpl(&mockWrapper); - - // Set up expectations for common RBUS operations - EXPECT_CALL(mockWrapper, rbusValue_Init(testing::_)) - .WillRepeatedly(testing::DoAll( - testing::SetArgPointee<0>((rbusValue_t)0x12345678), // Set to non-null dummy pointer - testing::Return(RBUS_ERROR_SUCCESS) - )); - EXPECT_CALL(mockWrapper, rbusValue_SetString(testing::_, testing::_)) - .WillRepeatedly(testing::Return(RBUS_ERROR_SUCCESS)); - EXPECT_CALL(mockWrapper, rbusProperty_SetValue(testing::_, testing::_)) - .WillRepeatedly(testing::Return()); - EXPECT_CALL(mockWrapper, rbusValue_Release(testing::_)) - .WillRepeatedly(testing::Return()); - - // Store original function pointers - orig_rbusProperty_GetName = rbusProperty_GetName; - orig_rbusProperty_GetValue = rbusProperty_GetValue; - orig_rbusValue_GetType = rbusValue_GetType; - orig_rbusValue_GetString = rbusValue_GetString; - orig_rbusProperty_SetValue = rbusProperty_SetValue; - orig_rbusValue_Release = rbusValue_Release; - - // Redirect to mock implementations - rbusProperty_GetName = mock_rbusProperty_GetName; - rbusProperty_GetValue = mock_rbusProperty_GetValue; - rbusValue_GetType = mock_rbusValue_GetType; - rbusValue_GetString = mock_rbusValue_GetString; - rbusProperty_SetValue = mock_rbusProperty_SetValue; - rbusValue_Release = mock_rbusValue_Release; - } - - void TearDown() override { - // Clean up test files - unlink(RRD_PROFILE_CATEGORY_FILE); - - // Reset global state - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - strcpy(RRDProfileCategory, "all"); - - // Reset global mock property properly (don't use memset on C++ objects) - g_mockRbusProperty.name.clear(); - g_mockRbusProperty.value.clear(); - g_mockRbusProperty.type = RBUS_STRING; - - // Clear RBusApiWrapper implementation - RBusApiWrapper::clearImpl(); - - // Restore original function pointers - rbusProperty_GetName = orig_rbusProperty_GetName; - rbusProperty_GetValue = orig_rbusProperty_GetValue; - rbusValue_GetType = orig_rbusValue_GetType; - rbusValue_GetString = orig_rbusValue_GetString; - rbusProperty_SetValue = orig_rbusProperty_SetValue; - rbusValue_Release = orig_rbusValue_Release; - } -}; - -/* --------------- Test rrd_SetHandler() --------------- */ - -TEST_F(RRDProfileHandlerTest, SetHandler_ValidStringAll) -{ - // Setup mock RBUS property - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = "all"; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusSetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); - - EXPECT_EQ(result, RBUS_ERROR_SUCCESS); - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -TEST_F(RRDProfileHandlerTest, SetHandler_ValidStringCategory) -{ - // Setup mock RBUS property - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = "Video"; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusSetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); - - EXPECT_EQ(result, RBUS_ERROR_SUCCESS); - EXPECT_STREQ(RRDProfileCategory, "Video"); -} - -TEST_F(RRDProfileHandlerTest, SetHandler_StringTooLong) -{ - // Create a string longer than 255 characters - std::string longString(300, 'A'); - - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = longString; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusSetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); - - EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); - // RRDProfileCategory should remain unchanged - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -TEST_F(RRDProfileHandlerTest, SetHandler_InvalidType) -{ - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = "Network"; - g_mockRbusProperty.type = RBUS_INT32; // Invalid type for this parameter - - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusSetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); - - EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -TEST_F(RRDProfileHandlerTest, SetHandler_WrongPropertyName) -{ - g_mockRbusProperty.name = "wrong.property.name"; - g_mockRbusProperty.value = "Audio"; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusSetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); - - EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -/* --------------- Test rrd_GetHandler() --------------- */ - -TEST_F(RRDProfileHandlerTest, GetHandler_AllCategories) -{ - // Override the filename in get handler to use our test JSON - // We'll need to modify the function to accept a test file path - - strcpy(RRDProfileCategory, "all"); - - g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusGetHandlerOptions_t* opts = nullptr; - - // Note: The actual function reads from "/etc/rrd/remote_debugger.json" - // For testing, we would need to either: - // 1. Create that file with test data, or - // 2. Modify the function to accept a test file parameter - // For now, we'll test the logic with a file that doesn't exist - rbusError_t result = rrd_GetHandler(nullptr, mockProp, opts); - - // Expect BUS_ERROR because test file doesn't exist at expected location - EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); -} - -TEST_F(RRDProfileHandlerTest, GetHandler_SpecificCategory) -{ - strcpy(RRDProfileCategory, "Network"); - - g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusGetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_GetHandler(nullptr, mockProp, opts); - - // Expect BUS_ERROR because test file doesn't exist at expected location - EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); -} - -TEST_F(RRDProfileHandlerTest, GetHandler_WrongPropertyName) -{ - g_mockRbusProperty.name = "wrong.property.name"; - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusGetHandlerOptions_t* opts = nullptr; - - rbusError_t result = rrd_GetHandler(nullptr, mockProp, opts); - - EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); -} - -/* --------------- Test helper functions --------------- */ - -TEST_F(RRDProfileHandlerTest, ReadProfileJsonFile_ValidFile) -{ - long file_size = 0; - const char* filepath = find_test_file("profileTestValid.json"); - ASSERT_NE(filepath, nullptr) << "Could not find profileTestValid.json in any search path"; - - char* result = read_profile_json_file(filepath, &file_size); - - ASSERT_NE(result, nullptr); - EXPECT_GT(file_size, 0); - EXPECT_NE(strstr(result, "Video"), nullptr); - EXPECT_NE(strstr(result, "Audio"), nullptr); - EXPECT_NE(strstr(result, "Network"), nullptr); - EXPECT_NE(strstr(result, "System"), nullptr); - - free(result); -} - -TEST_F(RRDProfileHandlerTest, ReadProfileJsonFile_NonExistentFile) -{ - long file_size = 0; - char* result = read_profile_json_file("/nonexistent/file.json", &file_size); - - EXPECT_EQ(result, nullptr); - EXPECT_EQ(file_size, 0); -} - -TEST_F(RRDProfileHandlerTest, GetSpecificCategoryJson_InvalidCategory) -{ - // Parse our test JSON - long file_size = 0; - const char* filepath = find_test_file("profileTestValid.json"); - ASSERT_NE(filepath, nullptr) << "Could not find profileTestValid.json in any search path"; - - char* jsonBuffer = read_profile_json_file(filepath, &file_size); - - ASSERT_NE(jsonBuffer, nullptr); - - cJSON* json = cJSON_Parse(jsonBuffer); - ASSERT_NE(json, nullptr); - - char* result = get_specific_category_json(json, "NonExistentCategory"); - ASSERT_NE(result, nullptr); - - cJSON_Delete(json); - free(jsonBuffer); - free(result); -} - -/* --------------- Test JSON parsing error handling --------------- */ - -TEST_F(RRDProfileHandlerTest, ParseInvalidJson) -{ - // Test with invalid JSON file - long file_size = 0; - const char* filepath = find_test_file("profileTestInvalid.json"); - ASSERT_NE(filepath, nullptr) << "Could not find profileTestInvalid.json in any search path"; - - char* jsonBuffer = read_profile_json_file(filepath, &file_size); - - ASSERT_NE(jsonBuffer, nullptr); - - cJSON* json = cJSON_Parse(jsonBuffer); - EXPECT_EQ(json, nullptr); // Should fail to parse - - // Clean up - free(jsonBuffer); -} - -TEST_F(RRDProfileHandlerTest, SetRbusResponse_ValidInput) -{ - g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - - const char* testJson = "{\"test\": \"value\"}"; - - rbusError_t result = set_rbus_response(mockProp, testJson); - - EXPECT_EQ(result, RBUS_ERROR_SUCCESS); - // Note: Response verification depends on implementation -} - -TEST_F(RRDProfileHandlerTest, SetRbusResponse_NullInput) -{ - g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - - rbusError_t result = set_rbus_response(mockProp, nullptr); - - EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); -} - -/* --------------- Test profile category file operations --------------- */ - -TEST_F(RRDProfileHandlerTest, SaveAndLoadProfileCategory) -{ - // Test saving a category - strcpy(RRDProfileCategory, "Network"); - int saveResult = save_profile_category(); - EXPECT_EQ(saveResult, 0); - - // Clear the global variable - memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); - strcpy(RRDProfileCategory, "default"); - - // Test loading the category - int loadResult = load_profile_category(); - EXPECT_EQ(loadResult, 0); - EXPECT_STREQ(RRDProfileCategory, "Network"); -} - -TEST_F(RRDProfileHandlerTest, LoadProfileCategory_NoFile) -{ - // Ensure file doesn't exist - unlink(RRD_PROFILE_CATEGORY_FILE); - - int result = load_profile_category(); - EXPECT_NE(result, 0); - EXPECT_STREQ(RRDProfileCategory, "all"); -} - -/* --------------- Integration tests for complete workflow --------------- */ - -TEST_F(RRDProfileHandlerTest, SetAndGetWorkflow_AllCategories) -{ - // Test complete workflow: set "all" -> get should return all categories - - // Step 1: Set profile category to "all" - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = "all"; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockSetProp = (rbusProperty_t)&g_mockRbusProperty; - rbusError_t setResult = rrd_SetHandler(nullptr, mockSetProp, nullptr); - - EXPECT_EQ(setResult, RBUS_ERROR_SUCCESS); - EXPECT_STREQ(RRDProfileCategory, "all"); - - // Step 2: Get profile data (will fail because file doesn't exist at expected path) - g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; - rbusProperty_t mockGetProp = (rbusProperty_t)&g_mockRbusProperty; - - rbusError_t getResult = rrd_GetHandler(nullptr, mockGetProp, nullptr); - EXPECT_EQ(getResult, RBUS_ERROR_BUS_ERROR); // Expected since file doesn't exist -} - -TEST_F(RRDProfileHandlerTest, SetAndGetWorkflow_SpecificCategory) -{ - // Test complete workflow: set "System" -> get should return System category only - - // Step 1: Set profile category to specific category - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = "System"; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockSetProp = (rbusProperty_t)&g_mockRbusProperty; - rbusError_t setResult = rrd_SetHandler(nullptr, mockSetProp, nullptr); - - EXPECT_EQ(setResult, RBUS_ERROR_SUCCESS); - EXPECT_STREQ(RRDProfileCategory, "System"); - - // Step 2: Verify the category was persisted - // Clear global and reload from file - strcpy(RRDProfileCategory, "default"); - load_profile_category(); - EXPECT_STREQ(RRDProfileCategory, "System"); -} - -/* --------------- Boundary and stress tests --------------- */ - -TEST_F(RRDProfileHandlerTest, SetHandler_EmptyString) -{ - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = ""; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockProp = (rbusProperty_t)&mockRBusApi; - rbusError_t result = rrd_SetHandler(nullptr, mockProp, nullptr); - - EXPECT_EQ(result, RBUS_ERROR_SUCCESS); - EXPECT_STREQ(RRDProfileCategory, ""); -} - -TEST_F(RRDProfileHandlerTest, SetHandler_MaxLengthString) -{ - // Create a string of exactly 255 characters (max allowed) - std::string maxString(255, 'A'); - - g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; - g_mockRbusProperty.value = maxString; - g_mockRbusProperty.type = RBUS_STRING; - - rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; - rbusError_t result = rrd_SetHandler(nullptr, mockProp, nullptr); - - EXPECT_EQ(result, RBUS_ERROR_SUCCESS); - EXPECT_STREQ(RRDProfileCategory, maxString.c_str()); -} From d90844cda5d34902d7dfac27ea4a230a1ac0837e Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Tue, 19 May 2026 23:57:59 +0530 Subject: [PATCH 3/6] Update rrdUnitTestRunner.cpp --- src/unittest/rrdUnitTestRunner.cpp | 1452 +++++++++++++++++++++++++++- 1 file changed, 1445 insertions(+), 7 deletions(-) diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index eb277d002..c7f09e9bd 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -84,6 +84,32 @@ #define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" #define GTEST_DEFAULT_RESULT_FILENAME "rdkRemoteDebugger_gtest_report.json" #define GTEST_REPORT_FILEPATH_SIZE 256 +// Define test data directory - use relative path that works from test execution context +#define TEST_DATA_DIR "src/unittest/UTJson/" + +// Helper function to find test files with fallback paths +static const char* find_test_file(const char* filename) { + static char filepath[512]; + const char* search_paths[] = { + "UTJson/", + "src/unittest/UTJson/", + "./UTJson/", + "./src/unittest/UTJson/", + "../src/unittest/UTJson/", + "../../src/unittest/UTJson/", + NULL + }; + + for (int i = 0; search_paths[i] != NULL; i++) { + snprintf(filepath, sizeof(filepath), "%s%s", search_paths[i], filename); + FILE* f = fopen(filepath, "r"); + if (f) { + fclose(f); + return filepath; + } + } + return NULL; // File not found in any path +} using namespace std; using ::testing::_; @@ -968,6 +994,7 @@ TEST(RRDGetProfileStringLengthTest, HandlesDeepSleepAwakeEventEmptyProfile) free(issue.Node); free(issue.subNode); } + #endif /* --------------- Test RRDCheckIssueInDynamicProfile() from rrdDeepSleep --------------- */ class RRDCheckIssueInDynamicProfileTest : public ::testing::Test @@ -1892,11 +1919,13 @@ TEST_F(RemoveItemTest, HandlesCacheNotNullAndCacheNotEqualsRrdCachecnode) node->mdata = strdup("PkgData"); node->issueString = strdup("IssueString"); node->next = NULL; + node->prev = NULL; cacheDataNode = node; cacheData *node_dummy = (cacheData *)malloc(sizeof(cacheData)); node_dummy->mdata = strdup("PkgData"); node_dummy->issueString = strdup("IssueString"); node_dummy->next = NULL; + node_dummy->prev = NULL; remove_item(node_dummy); EXPECT_NE(cacheDataNode, nullptr); @@ -1956,15 +1985,19 @@ TEST(RemoveSpecialCharacterfromIssueTypeListTest, HandlesStringWithConsecutiveSp /* --------------- Test issueTypeSplitter() from rrdEventProcess --------------- */ TEST(IssueTypeSplitterTest, HandlesStringWithSpecialCharacters) { + /* issueTypeSplitter now performs pure token splitting only; special-character + * removal is done separately by the caller (processIssueTypeEvent) on the + * extracted base, so raw tokens including special chars are returned here. */ char str[] = "a@,b,&,cd,ef"; char **args = NULL; int count = issueTypeSplitter(str, ',', &args); - ASSERT_EQ(count, 4); - ASSERT_STREQ(args[0], "a"); + ASSERT_EQ(count, 5); + ASSERT_STREQ(args[0], "a@"); ASSERT_STREQ(args[1], "b"); - ASSERT_STREQ(args[2], "cd"); - ASSERT_STREQ(args[3], "ef"); + ASSERT_STREQ(args[2], "&"); + ASSERT_STREQ(args[3], "cd"); + ASSERT_STREQ(args[4], "ef"); for (int i = 0; i < count; i++) { @@ -2000,6 +2033,237 @@ TEST(IssueTypeSplitterTest, HandlesEmptyString) free(args); } +/* --------------- Test split_issue_type() from rrdJsonParser --------------- */ +TEST(SplitIssueTypeTest, NoUnderscoreReturnsFull) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, UnderscoreSplitsBaseAndSuffix) +{ + /* Short suffix (total length including '_' is ≤ 9) is accepted and preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab12345", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab12345"); +} + +TEST(SplitIssueTypeTest, MultipleUnderscoresSplitsAtFirst) +{ + /* "abc_def_ghi": suffix "_def_ghi" is 8 chars (≤ 9) → accepted and preserved. + * Only the first '_' is used as the split point; base never contains '_'. */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_def_ghi", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_def_ghi"); +} + +TEST(SplitIssueTypeTest, EmptyInputProducesEmptyOutputs) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullInputDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* Should return without crashing and clear provided outputs to empty strings */ + split_issue_type(NULL, base, sizeof(base), suffix, sizeof(suffix)); + /* NULL input clears the output buffers when buffer pointers are provided */ + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, BaseTruncatedWhenTooSmall) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9) → accepted. + * Base = "abc" (before '_'); with a 4-byte buffer this fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, SuffixTruncatedWhenTooSmall) +{ + /* "abc_12345678": suffix "_12345678" is 9 chars (≤ 9, accepted). Suffix buffer + * is only 5 bytes so suffix is truncated to "_123" + NUL. */ + char base[64] = {0}; + char suffix[5] = {0}; + split_issue_type("abc_12345678", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_123"); + EXPECT_EQ(suffix[sizeof(suffix) - 1], '\0'); + EXPECT_EQ(strlen(suffix), (size_t)(sizeof(suffix) - 1)); +} + +TEST(SplitIssueTypeTest, LeadingUnderscoreGivesEmptyBase) +{ + /* "_suffixonly": split at '_' gives empty base; suffix "_suffixonly" is 11 chars + * (> 9) so it is discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_suffixonly", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullBaseDoesNotCrash) +{ + char suffix[64] = {0}; + /* NULL base pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", NULL, 64, suffix, sizeof(suffix)); + /* suffix remains unchanged when base is NULL */ + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, NullSuffixDoesNotCrash) +{ + char base[64] = {0}; + /* NULL suffix pointer: should return without crashing */ + split_issue_type("Device.DeviceTime_Search", base, sizeof(base), NULL, 64); + /* base remains unchanged when suffix is NULL */ + EXPECT_STREQ(base, ""); +} + +TEST(SplitIssueTypeTest, ZeroBaseLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* base_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, 0, suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ZeroSuffixLenDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + /* suffix_len == 0: should return without writing anything */ + split_issue_type("abc_def", base, sizeof(base), suffix, 0); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, ExactFitBase) +{ + /* "abc_suffix": suffix "_suffix" is 7 chars (≤ 9, accepted). + * Base = "abc" (before '_'); 4-byte buffer fits exactly. */ + char base[4] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_EQ(base[3], '\0'); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST(SplitIssueTypeTest, OnlyUnderscoreInput) +{ + /* "_": split at '_' gives empty base; suffix is "_" (1 char, ≤ 9 → accepted) */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, NineCharSuffixIsAccepted) +{ + /* Suffix of exactly 9 chars (the upper boundary, inclusive) must be accepted */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_12345678", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_12345678"); +} + +TEST(SplitIssueTypeTest, LongSuffixIsDiscarded) +{ + /* Suffix longer than 9 chars is discarded regardless of its content */ + char base[64] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceInfo_1234567890", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceInfo"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixExceedingMaxLengthDiscarded) +{ + /* "_Random-token" is 13 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_Random-token", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixSeventeenCharsDiscarded) +{ + /* "_Search_something" is 17 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_Search_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixTwentyCharsDiscarded) +{ + /* "_LogSearch_something" is 20 chars (> 9) → suffix discarded */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_LogSearch_something", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, ""); +} + +TEST(SplitIssueTypeTest, SuffixWithUnsafeCharsIsSanitized) +{ + /* "_ab;rm" is 6 chars (≤ 9, accepted) but ';' is unsafe and must be stripped. + * Expected sanitized suffix: "_abrm" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab;rm", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_abrm"); +} + +TEST(SplitIssueTypeTest, SuffixWithOnlyUnsafeCharsBecomesUnderscore) +{ + /* "_!@#" is 4 chars (≤ 9, accepted length-wise) but all payload chars are unsafe. + * After sanitization only the leading '_' remains → suffix="_" */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("abc_!@#", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "abc"); + EXPECT_STREQ(suffix, "_"); +} + +TEST(SplitIssueTypeTest, SuffixHyphensPreserved) +{ + /* "_ab-cd" is 6 chars (≤ 9, accepted) and '-' is in the safe set → preserved */ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_ab-cd", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_ab-cd"); +} + /* --------------- Test processIssueTypeInDynamicProfile() from rrdEventProcess --------------- */ class ProcessIssueTypeInDynamicProfileTest : public ::testing::Test { @@ -2082,11 +2346,93 @@ TEST(ProcessIssueTypeEvntTest, RBufIsNull){ } TEST(ProcessIssueTypeEvntTest, inDynamic_NoJson){ - data_buf rbuf; + data_buf rbuf = {}; rbuf.mdata = strdup("a"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceTime" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithLogSearchSuffix_inDynamic_NoJson){ + /* Issue type with a long suffix (> 9 chars): suffix is discarded; base "Device.DeviceInfo" is used */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceInfo_LogSearch-9abc1def-0000-1111-2222-3333aaaabbbb"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, IssueTypeWithInvalidSuffixTreatedAsBase) +{ + /* "_Random-token" is 13 chars (> 9): suffix discarded; base = "Device.DeviceTime" */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime_Random-token"); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Should not crash; long suffix is discarded, base is processed normally */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, MultipleIssueTypesWithAndWithoutSuffix){ + /* Comma-separated list: one plain type, one with long suffix (> 9, discarded), + * one with another long suffix (> 9, discarded) */ + data_buf rbuf = {}; + rbuf.mdata = strdup("Device.DeviceTime,Device.DeviceInfo_Search-1234,Device.Net_BadSuffix"); + rbuf.inDynamic = true; + rbuf.jsonPath = nullptr; + /* Should not crash; all entries are processed without leaks */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, WhitespaceOnlyIssueTypeIsSkipped) +{ + /* When the IssueType value from RBUS is whitespace (e.g. a space), + * removeSpecialCharacterfromIssueTypeList() strips it to an empty string. + * processIssueTypeEvent() must detect the post-sanitization empty base + * and skip processing without crashing or invoking processIssueType. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(" "); /* single space — all-special after split */ + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + /* Must not crash and must not reach getIssueInfo with an empty mdata */ + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; +} + +TEST(ProcessIssueTypeEvntTest, EmptyStringIssueTypeIsSkipped) +{ + /* An empty-string mdata must be handled gracefully — issueTypeSplitter + * returns 1 token that is an empty string, which split_issue_type then + * maps to an empty base, causing the entry to be skipped. */ + data_buf rbuf = {}; + rbuf.mdata = strdup(""); + rbuf.inDynamic = false; + rbuf.jsonPath = nullptr; + processIssueTypeEvent(&rbuf); + free(rbuf.mdata); + rbuf.mdata = NULL; } /* ======================== rrdExecuteScript ==============*/ @@ -2210,12 +2556,22 @@ TEST(RRDDataBuffInitTest, InitializeDataBuff) EXPECT_EQ(sbuf.dsEvent, deepSleepEvent); } +TEST(RRDDataBuffInitTest, SuffixInitializedToNull) +{ + /* Verify that the newly added suffix field is initialised to NULL */ + data_buf sbuf; + sbuf.suffix = reinterpret_cast(0xDEADBEEF); /* pre-fill with garbage */ + RRD_data_buff_init(&sbuf, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); + EXPECT_EQ(sbuf.suffix, nullptr); +} + /* --------------- Test RRD_data_buff_deAlloc() from rrdIarm --------------- */ TEST(RRDDataBuffDeAllocTest, DeallocateDataBuff) { data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); sbuf->mdata = (char *)malloc(10 * sizeof(char)); sbuf->jsonPath = (char *)malloc(10 * sizeof(char)); + sbuf->suffix = nullptr; ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } @@ -2227,6 +2583,28 @@ TEST(RRDDataBuffDeAllocTest, NullPointer) ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); } +TEST(RRDDataBuffDeAllocTest, DeallocateWithSuffixSet) +{ + /* Verify that suffix is freed without crash when it is non-NULL */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = strdup("IssueType"); + sbuf->jsonPath = nullptr; + sbuf->suffix = strdup("_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + +TEST(RRDDataBuffDeAllocTest, DeallocateWithAllFieldsNull) +{ + /* All pointer fields NULL: should not crash */ + data_buf *sbuf = (data_buf *)malloc(sizeof(data_buf)); + sbuf->mdata = nullptr; + sbuf->jsonPath = nullptr; + sbuf->suffix = nullptr; + + ASSERT_NO_FATAL_FAILURE(RRD_data_buff_deAlloc(sbuf)); +} + /* --------------- Test RRD_unsubscribe() from rrdIarm --------------- */ class RRDUnsubscribeTest : public ::testing::Test @@ -3777,6 +4155,7 @@ TEST_F(RRDEventThreadFuncTest, MessageReceiveSuccessEventMsgType) { rbuf.mdata = strdup("Test"); rbuf.inDynamic = true; rbuf.jsonPath = nullptr; + rbuf.suffix = strdup("_ab12345"); msgRRDHdr msgHdr; msgHdr.mbody = malloc(sizeof(data_buf)); ASSERT_NE(msgHdr.mbody, nullptr); @@ -4537,12 +4916,22 @@ TEST_F(RRDUploadOrchestrationTest, SpecialCharactersInIssueType) { char sanitized[64]; int result = rrd_logproc_convert_issue_type("test-issue.sub@special!", sanitized, sizeof(sanitized)); EXPECT_EQ(result, 0); - // Should only contain alphanumeric and underscore + // Should only contain alphanumeric, underscore, and hyphen for (const char *p = sanitized; *p; ++p) { - EXPECT_TRUE(isalnum(*p) || *p == '_'); + EXPECT_TRUE(isalnum(*p) || *p == '_' || *p == '-'); } } +// Suffix with hyphens: hyphens must be preserved so portal can parse filename +TEST_F(RRDUploadOrchestrationTest, IssueTypeWithSuffixHyphensPreserved) { + char sanitized[128]; + // Simulates issue type after normalizeIssueName: dots→underscore, hyphens kept + int result = rrd_logproc_convert_issue_type("Device_DeviceIP_Search-67768-67", sanitized, sizeof(sanitized)); + EXPECT_EQ(result, 0); + // Hyphens in suffix must survive so the archive filename delimiter structure is intact + EXPECT_STREQ(sanitized, "DEVICE_DEVICEIP_SEARCH-67768-67"); +} + // Performance test: Large directory TEST_F(RRDUploadOrchestrationTest, LargeDirectoryHandling) { // Create multiple log files @@ -4860,6 +5249,1055 @@ TEST_F(RRDUploadOrchestrationTest, PriorityAdjustment) { } +/* ====================== Profile Management Function Tests ================*/ +/* --------------- Test load_profile_category() from rrdInterface --------------- */ +class LoadProfileCategoryTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Clean up any existing test file + remove(RRD_PROFILE_CATEGORY_FILE); + + // Reset global category + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + } + + void TearDown() override + { + // Clean up test file + remove(RRD_PROFILE_CATEGORY_FILE); + + // Reset global category + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + } +}; + +TEST_F(LoadProfileCategoryTest, LoadFromExistingFile) +{ + // Create test file with category + FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "Video\n"); + fclose(fp); + + int result = load_profile_category(); + EXPECT_EQ(result, 0); + EXPECT_STREQ(RRDProfileCategory, "Video"); +} + +TEST_F(LoadProfileCategoryTest, LoadFromNonExistentFile) +{ + int result = load_profile_category(); + EXPECT_EQ(result, -1); + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +TEST_F(LoadProfileCategoryTest, LoadFromEmptyFile) +{ + // Create empty file + FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w"); + ASSERT_NE(fp, nullptr); + fclose(fp); + + int result = load_profile_category(); + EXPECT_EQ(result, -1); + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +TEST_F(LoadProfileCategoryTest, LoadWithNewlineHandling) +{ + // Create test file with multiple lines + FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "Network\nextra line"); + fclose(fp); + + int result = load_profile_category(); + EXPECT_EQ(result, 0); + EXPECT_STREQ(RRDProfileCategory, "Network"); +} + +/* --------------- Test save_profile_category() from rrdInterface --------------- */ +class SaveProfileCategoryTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Clean up any existing test file + remove(RRD_PROFILE_CATEGORY_FILE); + + // Set test category + strncpy(RRDProfileCategory, "Audio", sizeof(RRDProfileCategory) - 1); + RRDProfileCategory[sizeof(RRDProfileCategory) - 1] = '\0'; + } + + void TearDown() override + { + // Clean up test file + remove(RRD_PROFILE_CATEGORY_FILE); + + // Reset global category + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + } +}; + +TEST_F(SaveProfileCategoryTest, SaveToFile) +{ + int result = save_profile_category(); + EXPECT_EQ(result, 0); + + // Verify file was created and contains correct content + FILE *fp = fopen(RRD_PROFILE_CATEGORY_FILE, "r"); + ASSERT_NE(fp, nullptr); + + char buffer[256]; + ASSERT_NE(fgets(buffer, sizeof(buffer), fp), nullptr); + fclose(fp); + + // Remove newline for comparison + char *newline = strchr(buffer, '\n'); + if (newline) *newline = '\0'; + + EXPECT_STREQ(buffer, "Audio"); +} + +TEST_F(SaveProfileCategoryTest, SaveToReadOnlyDirectory) +{ + // This test checks behavior when file cannot be written + // Create a scenario where the directory might not be writable + // The function should return -1 in error cases + + // We can't easily test read-only scenarios in unit tests, + // but we can verify the function handles file creation properly + int result = save_profile_category(); + + // Should succeed in normal test environment + EXPECT_GE(result, -1); // Either success (0) or expected failure (-1) +} + +/* --------------- Test has_direct_commands() from rrdInterface --------------- */ +class HasDirectCommandsTest : public ::testing::Test +{ +protected: + cJSON *category; + + void SetUp() override + { + category = nullptr; + } + + void TearDown() override + { + if (category) { + cJSON_Delete(category); + } + } +}; + +TEST_F(HasDirectCommandsTest, CategoryWithDirectCommands) +{ + // Create category with direct commands structure + const char *json_str = R"({ + "IssueType1": { + "Commands": "ls -la" + }, + "IssueType2": { + "Commands": "ps aux" + } + })"; + + category = cJSON_Parse(json_str); + ASSERT_NE(category, nullptr); + + bool result = has_direct_commands(category); + EXPECT_TRUE(result); +} + +TEST_F(HasDirectCommandsTest, CategoryWithoutDirectCommands) +{ + // Create category without Commands field + const char *json_str = R"({ + "IssueType1": { + "Description": "Test issue" + }, + "IssueType2": { + "Timeout": 30 + } + })"; + + category = cJSON_Parse(json_str); + ASSERT_NE(category, nullptr); + + bool result = has_direct_commands(category); + EXPECT_FALSE(result); +} + +TEST_F(HasDirectCommandsTest, EmptyCategory) +{ + category = cJSON_CreateObject(); + ASSERT_NE(category, nullptr); + + bool result = has_direct_commands(category); + EXPECT_FALSE(result); +} + +TEST_F(HasDirectCommandsTest, NullCategory) +{ + bool result = has_direct_commands(nullptr); + EXPECT_FALSE(result); +} + +/* --------------- Test read_profile_json_file() from rrdInterface --------------- */ +class ReadProfileJsonFileTest : public ::testing::Test +{ +protected: + const char *test_file = "/tmp/test_profile.json"; + long file_size; + + void SetUp() override + { + file_size = 0; + } + + void TearDown() override + { + remove(test_file); + } +}; + +TEST_F(ReadProfileJsonFileTest, ReadValidFile) +{ + // Create test file with JSON content + const char *json_content = R"({"Video": {"issue1": {"Commands": "test"}}})"; + FILE *fp = fopen(test_file, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "%s", json_content); + fclose(fp); + + char *result = read_profile_json_file(test_file, &file_size); + ASSERT_NE(result, nullptr); + EXPECT_GT(file_size, 0); + EXPECT_STREQ(result, json_content); + + free(result); +} + +TEST_F(ReadProfileJsonFileTest, ReadNonExistentFile) +{ + char *result = read_profile_json_file("/tmp/nonexistent.json", &file_size); + EXPECT_EQ(result, nullptr); + EXPECT_EQ(file_size, 0); +} + +TEST_F(ReadProfileJsonFileTest, ReadEmptyFile) +{ + // Create empty file + FILE *fp = fopen(test_file, "w"); + ASSERT_NE(fp, nullptr); + fclose(fp); + + char *result = read_profile_json_file(test_file, &file_size); + EXPECT_EQ(result, nullptr); +} + +TEST_F(ReadProfileJsonFileTest, ReadNullFilename) +{ + char *result = read_profile_json_file(nullptr, &file_size); + EXPECT_EQ(result, nullptr); +} + +/* --------------- Test get_all_categories_json() from rrdInterface --------------- */ +class GetAllCategoriesJsonTest : public ::testing::Test +{ +protected: + cJSON *json; + + void SetUp() override + { + json = nullptr; + } + + void TearDown() override + { + if (json) { + cJSON_Delete(json); + } + } +}; + +TEST_F(GetAllCategoriesJsonTest, GetAllValidCategories) +{ + // Create JSON with multiple categories + const char *json_str = R"({ + "Video": { + "issue1": {"Commands": "test1"}, + "issue2": {"Commands": "test2"} + }, + "Audio": { + "issue3": {"Commands": "test3"} + } + })"; + + json = cJSON_Parse(json_str); + ASSERT_NE(json, nullptr); + + char *result = get_all_categories_json(json); + ASSERT_NE(result, nullptr); + + // Parse result to verify structure + cJSON *result_json = cJSON_Parse(result); + ASSERT_NE(result_json, nullptr); + + // Check that Video and Audio categories exist + cJSON *video = cJSON_GetObjectItem(result_json, "Video"); + cJSON *audio = cJSON_GetObjectItem(result_json, "Audio"); + + EXPECT_NE(video, nullptr); + EXPECT_NE(audio, nullptr); + EXPECT_TRUE(cJSON_IsArray(video)); + EXPECT_TRUE(cJSON_IsArray(audio)); + + cJSON_Delete(result_json); + free(result); +} + +TEST_F(GetAllCategoriesJsonTest, GetAllFromEmptyJson) +{ + json = cJSON_CreateObject(); + ASSERT_NE(json, nullptr); + + char *result = get_all_categories_json(json); + ASSERT_NE(result, nullptr); + + // Should return empty object + cJSON *result_json = cJSON_Parse(result); + ASSERT_NE(result_json, nullptr); + EXPECT_EQ(cJSON_GetArraySize(result_json), 0); + + cJSON_Delete(result_json); + free(result); +} + +TEST_F(GetAllCategoriesJsonTest, GetAllFromNullJson) +{ + char *result = get_all_categories_json(nullptr); + ASSERT_NE(result, nullptr); + + // NULL input should return an empty JSON object + cJSON *result_json = cJSON_Parse(result); + ASSERT_NE(result_json, nullptr); + EXPECT_EQ(cJSON_GetArraySize(result_json), 0); + + cJSON_Delete(result_json); + free(result); +} + +/* --------------- Test get_specific_category_json() from rrdInterface --------------- */ +class GetSpecificCategoryJsonTest : public ::testing::Test +{ +protected: + cJSON *json; + + void SetUp() override + { + json = nullptr; + } + + void TearDown() override + { + if (json) { + cJSON_Delete(json); + } + } +}; + +TEST_F(GetSpecificCategoryJsonTest, GetExistingCategory) +{ + const char *json_str = R"({ + "Video": { + "issue1": {"Commands": "test1"}, + "issue2": {"Commands": "test2"} + }, + "Audio": { + "issue3": {"Commands": "test3"} + } + })"; + + json = cJSON_Parse(json_str); + ASSERT_NE(json, nullptr); + + char *result = get_specific_category_json(json, "Video"); + ASSERT_NE(result, nullptr); + + // Parse result to verify it's an array with Video issues + cJSON *result_json = cJSON_Parse(result); + ASSERT_NE(result_json, nullptr); + EXPECT_TRUE(cJSON_IsArray(result_json)); + + cJSON_Delete(result_json); + free(result); +} + +TEST_F(GetSpecificCategoryJsonTest, GetNonExistentCategory) +{ + const char *json_str = R"({ + "Video": { + "issue1": {"Commands": "test1"} + } + })"; + + json = cJSON_Parse(json_str); + ASSERT_NE(json, nullptr); + + char *result = get_specific_category_json(json, "NonExistent"); + ASSERT_NE(result, nullptr); + + cJSON *result_json = cJSON_Parse(result); + ASSERT_NE(result_json, nullptr); + EXPECT_FALSE(cJSON_IsArray(result_json)); + EXPECT_EQ(cJSON_GetArraySize(result_json), 1); + + cJSON_Delete(result_json); + free(result); +} + +TEST_F(GetSpecificCategoryJsonTest, GetFromNullJson) +{ + char *result = get_specific_category_json(nullptr, "Video"); + // Should handle null input gracefully + ASSERT_NE(result, nullptr); + + cJSON *result_json = cJSON_Parse(result); + ASSERT_NE(result_json, nullptr); + EXPECT_FALSE(cJSON_IsArray(result_json)); + + cJSON_Delete(result_json); + free(result); +} + +/* --------------- Test rrd_SetHandler() from rrdInterface --------------- */ +class RrdSetHandlerTest : public ::testing::Test +{ +protected: + MockRBusApi mock_rbus_api; + + void SetUp() override + { + // Clear any existing profile category + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + + // Clean up test file + remove(RRD_PROFILE_CATEGORY_FILE); + } + + void TearDown() override + { + // Clean up + remove(RRD_PROFILE_CATEGORY_FILE); + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + } +}; + +TEST_F(RrdSetHandlerTest, SetValidProfileCategory) +{ + // This test verifies the overall logic without deep RBUS API testing + // since those are mocked and complex to set up properly + + // Set a test category directly to verify save/load workflow + strncpy(RRDProfileCategory, "TestCategory", sizeof(RRDProfileCategory) - 1); + + // Test save functionality + int save_result = save_profile_category(); + EXPECT_EQ(save_result, 0); + + // Clear and reload to verify + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + int load_result = load_profile_category(); + EXPECT_EQ(load_result, 0); + EXPECT_STREQ(RRDProfileCategory, "TestCategory"); +} + +/* --------------- Test rrd_GetHandler() from rrdInterface --------------- */ +class RrdGetHandlerTest : public ::testing::Test +{ +protected: + const char *test_json_file = "/tmp/test_profile.json"; + + void SetUp() override + { + // Create test JSON file + const char *json_content = R"({ + "Video": { + "issue1": {"Commands": "test1"}, + "issue2": {"Commands": "test2"} + }, + "Audio": { + "issue3": {"Commands": "test3"} + } + })"; + + FILE *fp = fopen(test_json_file, "w"); + if (fp) { + fprintf(fp, "%s", json_content); + fclose(fp); + } + + // Set up profile category + strncpy(RRDProfileCategory, "all", sizeof(RRDProfileCategory) - 1); + } + + void TearDown() override + { + remove(test_json_file); + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + } +}; + +TEST_F(RrdGetHandlerTest, TestProfileDataProcessing) +{ + // Test the helper functions used by rrd_GetHandler + + long file_size; + char *json_buffer = read_profile_json_file(test_json_file, &file_size); + ASSERT_NE(json_buffer, nullptr); + EXPECT_GT(file_size, 0); + + cJSON *json = cJSON_Parse(json_buffer); + ASSERT_NE(json, nullptr); + + // Test get_all_categories_json + char *all_result = get_all_categories_json(json); + ASSERT_NE(all_result, nullptr); + + // Test get_specific_category_json + char *specific_result = get_specific_category_json(json, "Video"); + ASSERT_NE(specific_result, nullptr); + + // Cleanup + cJSON_Delete(json); + free(json_buffer); + free(all_result); + free(specific_result); +} + +/* --------------- Test set_rbus_response() from rrdInterface --------------- */ +class SetRbusResponseTest : public ::testing::Test +{ +protected: + MockRBusApi mock_rbus_api; + + void SetUp() override + { + // Note: This is a complex function to test due to RBUS dependencies + // These tests verify the basic logic flow + } + + void TearDown() override + { + // Cleanup if needed + } +}; + +TEST_F(SetRbusResponseTest, HandlesNullJsonString) +{ + // Test with null JSON string - should return error + rbusError_t result = set_rbus_response(nullptr, nullptr); + EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); +} + +TEST_F(SetRbusResponseTest, HandlesValidJsonString) +{ + // This is difficult to test without full RBUS mock setup + // The function should succeed with valid inputs in a real environment + const char *test_json = R"({"test": "data"})"; + + // Without full RBUS setup, we can't fully test this + // But we can verify it handles the null case properly + rbusError_t result = set_rbus_response(nullptr, test_json); + // Expected behavior depends on RBUS implementation details +} + +/* ====================== rrd_SetHandler and rrd_GetHandler ================*/ + +// Simple mock for RBUS profile handler tests +class RBusProfileMock { +public: + std::string mockPropertyName; + std::string mockPropertyValue; + rbusValueType_t mockValueType = RBUS_STRING; + std::string mockResponseValue; +}; + +// Global mock RBUS property for profile handler tests +struct MockRBusProperty { + std::string name; + std::string value; + rbusValueType_t type; +} g_mockRbusProperty; + +// Mock RBUS function implementations for profile handler tests +static char const* mock_rbusProperty_GetName(rbusProperty_t property) { + (void)property; + return g_mockRbusProperty.name.c_str(); +} + +static rbusValue_t mock_rbusProperty_GetValue(rbusProperty_t property) { + (void)property; + return (rbusValue_t)g_mockRbusProperty.value.c_str(); +} + +static rbusValueType_t mock_rbusValue_GetType(rbusValue_t value) { + (void)value; + return g_mockRbusProperty.type; +} + +static char const* mock_rbusValue_GetString(rbusValue_t value, int* len) { + (void)value; + if (len) *len = g_mockRbusProperty.value.length(); + return g_mockRbusProperty.value.c_str(); +} + +static void mock_rbusProperty_SetValue(rbusProperty_t property, rbusValue_t value) { + (void)property; (void)value; +} + +static void mock_rbusValue_Release(rbusValue_t value) { + (void)value; +} + +// External declarations for function pointers from Client_Mock.cpp +extern char const* (*rbusProperty_GetName)(rbusProperty_t); +extern rbusValue_t (*rbusProperty_GetValue)(rbusProperty_t); +extern rbusValueType_t (*rbusValue_GetType)(rbusValue_t); +extern char const* (*rbusValue_GetString)(rbusValue_t, int*); +extern void (*rbusProperty_SetValue)(rbusProperty_t, rbusValue_t); +extern void (*rbusValue_Release)(rbusValue_t); + +// Test fixture for RRD Profile Handler tests +class RRDProfileHandlerTest : public ::testing::Test { +protected: + RBusProfileMock mockRBusApi; + MockRBusApi mockWrapper; // Add mock for RBusApiWrapper + + // Store original function pointers + char const* (*orig_rbusProperty_GetName)(rbusProperty_t); + rbusValue_t (*orig_rbusProperty_GetValue)(rbusProperty_t); + rbusValueType_t (*orig_rbusValue_GetType)(rbusValue_t); + char const* (*orig_rbusValue_GetString)(rbusValue_t, int*); + void (*orig_rbusProperty_SetValue)(rbusProperty_t, rbusValue_t); + void (*orig_rbusValue_Release)(rbusValue_t); + + void SetUp() override { + // Reset global state + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + strcpy(RRDProfileCategory, "all"); + + // Reset mock RBUS data + mockRBusApi.mockPropertyName.clear(); + mockRBusApi.mockPropertyValue.clear(); + mockRBusApi.mockValueType = RBUS_STRING; + mockRBusApi.mockResponseValue.clear(); + + // Reset global mock property + g_mockRbusProperty.name.clear(); + g_mockRbusProperty.value.clear(); + g_mockRbusProperty.type = RBUS_STRING; + + // Clear any existing RBusApiWrapper implementation first + RBusApiWrapper::clearImpl(); + + // Set up RBusApiWrapper with mock implementation + RBusApiWrapper::setImpl(&mockWrapper); + + // Set up expectations for common RBUS operations + EXPECT_CALL(mockWrapper, rbusValue_Init(testing::_)) + .WillRepeatedly(testing::DoAll( + testing::SetArgPointee<0>((rbusValue_t)0x12345678), // Set to non-null dummy pointer + testing::Return(RBUS_ERROR_SUCCESS) + )); + EXPECT_CALL(mockWrapper, rbusValue_SetString(testing::_, testing::_)) + .WillRepeatedly(testing::Return(RBUS_ERROR_SUCCESS)); + EXPECT_CALL(mockWrapper, rbusProperty_SetValue(testing::_, testing::_)) + .WillRepeatedly(testing::Return()); + EXPECT_CALL(mockWrapper, rbusValue_Release(testing::_)) + .WillRepeatedly(testing::Return()); + + // Store original function pointers + orig_rbusProperty_GetName = rbusProperty_GetName; + orig_rbusProperty_GetValue = rbusProperty_GetValue; + orig_rbusValue_GetType = rbusValue_GetType; + orig_rbusValue_GetString = rbusValue_GetString; + orig_rbusProperty_SetValue = rbusProperty_SetValue; + orig_rbusValue_Release = rbusValue_Release; + + // Redirect to mock implementations + rbusProperty_GetName = mock_rbusProperty_GetName; + rbusProperty_GetValue = mock_rbusProperty_GetValue; + rbusValue_GetType = mock_rbusValue_GetType; + rbusValue_GetString = mock_rbusValue_GetString; + rbusProperty_SetValue = mock_rbusProperty_SetValue; + rbusValue_Release = mock_rbusValue_Release; + } + + void TearDown() override { + // Clean up test files + unlink(RRD_PROFILE_CATEGORY_FILE); + + // Reset global state + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + strcpy(RRDProfileCategory, "all"); + + // Reset global mock property properly (don't use memset on C++ objects) + g_mockRbusProperty.name.clear(); + g_mockRbusProperty.value.clear(); + g_mockRbusProperty.type = RBUS_STRING; + + // Clear RBusApiWrapper implementation + RBusApiWrapper::clearImpl(); + + // Restore original function pointers + rbusProperty_GetName = orig_rbusProperty_GetName; + rbusProperty_GetValue = orig_rbusProperty_GetValue; + rbusValue_GetType = orig_rbusValue_GetType; + rbusValue_GetString = orig_rbusValue_GetString; + rbusProperty_SetValue = orig_rbusProperty_SetValue; + rbusValue_Release = orig_rbusValue_Release; + } +}; + +/* --------------- Test rrd_SetHandler() --------------- */ + +TEST_F(RRDProfileHandlerTest, SetHandler_ValidStringAll) +{ + // Setup mock RBUS property + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = "all"; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusSetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); + + EXPECT_EQ(result, RBUS_ERROR_SUCCESS); + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +TEST_F(RRDProfileHandlerTest, SetHandler_ValidStringCategory) +{ + // Setup mock RBUS property + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = "Video"; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusSetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); + + EXPECT_EQ(result, RBUS_ERROR_SUCCESS); + EXPECT_STREQ(RRDProfileCategory, "Video"); +} + +TEST_F(RRDProfileHandlerTest, SetHandler_StringTooLong) +{ + // Create a string longer than 255 characters + std::string longString(300, 'A'); + + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = longString; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusSetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); + + EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); + // RRDProfileCategory should remain unchanged + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +TEST_F(RRDProfileHandlerTest, SetHandler_InvalidType) +{ + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = "Network"; + g_mockRbusProperty.type = RBUS_INT32; // Invalid type for this parameter + + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusSetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); + + EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +TEST_F(RRDProfileHandlerTest, SetHandler_WrongPropertyName) +{ + g_mockRbusProperty.name = "wrong.property.name"; + g_mockRbusProperty.value = "Audio"; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusSetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_SetHandler(nullptr, mockProp, opts); + + EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +/* --------------- Test rrd_GetHandler() --------------- */ + +TEST_F(RRDProfileHandlerTest, GetHandler_AllCategories) +{ + // Override the filename in get handler to use our test JSON + // We'll need to modify the function to accept a test file path + + strcpy(RRDProfileCategory, "all"); + + g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusGetHandlerOptions_t* opts = nullptr; + + // Note: The actual function reads from "/etc/rrd/remote_debugger.json" + // For testing, we would need to either: + // 1. Create that file with test data, or + // 2. Modify the function to accept a test file parameter + // For now, we'll test the logic with a file that doesn't exist + rbusError_t result = rrd_GetHandler(nullptr, mockProp, opts); + + // Expect BUS_ERROR because test file doesn't exist at expected location + EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); +} + +TEST_F(RRDProfileHandlerTest, GetHandler_SpecificCategory) +{ + strcpy(RRDProfileCategory, "Network"); + + g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusGetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_GetHandler(nullptr, mockProp, opts); + + // Expect BUS_ERROR because test file doesn't exist at expected location + EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); +} + +TEST_F(RRDProfileHandlerTest, GetHandler_WrongPropertyName) +{ + g_mockRbusProperty.name = "wrong.property.name"; + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusGetHandlerOptions_t* opts = nullptr; + + rbusError_t result = rrd_GetHandler(nullptr, mockProp, opts); + + EXPECT_EQ(result, RBUS_ERROR_INVALID_INPUT); +} + +/* --------------- Test helper functions --------------- */ + +TEST_F(RRDProfileHandlerTest, ReadProfileJsonFile_ValidFile) +{ + long file_size = 0; + const char* filepath = find_test_file("profileTestValid.json"); + ASSERT_NE(filepath, nullptr) << "Could not find profileTestValid.json in any search path"; + + char* result = read_profile_json_file(filepath, &file_size); + + ASSERT_NE(result, nullptr); + EXPECT_GT(file_size, 0); + EXPECT_NE(strstr(result, "Video"), nullptr); + EXPECT_NE(strstr(result, "Audio"), nullptr); + EXPECT_NE(strstr(result, "Network"), nullptr); + EXPECT_NE(strstr(result, "System"), nullptr); + + free(result); +} + +TEST_F(RRDProfileHandlerTest, ReadProfileJsonFile_NonExistentFile) +{ + long file_size = 0; + char* result = read_profile_json_file("/nonexistent/file.json", &file_size); + + EXPECT_EQ(result, nullptr); + EXPECT_EQ(file_size, 0); +} + +TEST_F(RRDProfileHandlerTest, GetSpecificCategoryJson_InvalidCategory) +{ + // Parse our test JSON + long file_size = 0; + const char* filepath = find_test_file("profileTestValid.json"); + ASSERT_NE(filepath, nullptr) << "Could not find profileTestValid.json in any search path"; + + char* jsonBuffer = read_profile_json_file(filepath, &file_size); + + ASSERT_NE(jsonBuffer, nullptr); + + cJSON* json = cJSON_Parse(jsonBuffer); + ASSERT_NE(json, nullptr); + + char* result = get_specific_category_json(json, "NonExistentCategory"); + ASSERT_NE(result, nullptr); + + cJSON_Delete(json); + free(jsonBuffer); + free(result); +} + +/* --------------- Test JSON parsing error handling --------------- */ + +TEST_F(RRDProfileHandlerTest, ParseInvalidJson) +{ + // Test with invalid JSON file + long file_size = 0; + const char* filepath = find_test_file("profileTestInvalid.json"); + ASSERT_NE(filepath, nullptr) << "Could not find profileTestInvalid.json in any search path"; + + char* jsonBuffer = read_profile_json_file(filepath, &file_size); + + ASSERT_NE(jsonBuffer, nullptr); + + cJSON* json = cJSON_Parse(jsonBuffer); + EXPECT_EQ(json, nullptr); // Should fail to parse + + // Clean up + free(jsonBuffer); +} + +TEST_F(RRDProfileHandlerTest, SetRbusResponse_ValidInput) +{ + g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + + const char* testJson = "{\"test\": \"value\"}"; + + rbusError_t result = set_rbus_response(mockProp, testJson); + + EXPECT_EQ(result, RBUS_ERROR_SUCCESS); + // Note: Response verification depends on implementation +} + +TEST_F(RRDProfileHandlerTest, SetRbusResponse_NullInput) +{ + g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + + rbusError_t result = set_rbus_response(mockProp, nullptr); + + EXPECT_EQ(result, RBUS_ERROR_BUS_ERROR); +} + +/* --------------- Test profile category file operations --------------- */ + +TEST_F(RRDProfileHandlerTest, SaveAndLoadProfileCategory) +{ + // Test saving a category + strcpy(RRDProfileCategory, "Network"); + int saveResult = save_profile_category(); + EXPECT_EQ(saveResult, 0); + + // Clear the global variable + memset(RRDProfileCategory, 0, sizeof(RRDProfileCategory)); + strcpy(RRDProfileCategory, "default"); + + // Test loading the category + int loadResult = load_profile_category(); + EXPECT_EQ(loadResult, 0); + EXPECT_STREQ(RRDProfileCategory, "Network"); +} + +TEST_F(RRDProfileHandlerTest, LoadProfileCategory_NoFile) +{ + // Ensure file doesn't exist + unlink(RRD_PROFILE_CATEGORY_FILE); + + int result = load_profile_category(); + EXPECT_NE(result, 0); + EXPECT_STREQ(RRDProfileCategory, "all"); +} + +/* --------------- Integration tests for complete workflow --------------- */ + +TEST_F(RRDProfileHandlerTest, SetAndGetWorkflow_AllCategories) +{ + // Test complete workflow: set "all" -> get should return all categories + + // Step 1: Set profile category to "all" + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = "all"; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockSetProp = (rbusProperty_t)&g_mockRbusProperty; + rbusError_t setResult = rrd_SetHandler(nullptr, mockSetProp, nullptr); + + EXPECT_EQ(setResult, RBUS_ERROR_SUCCESS); + EXPECT_STREQ(RRDProfileCategory, "all"); + + // Step 2: Get profile data (will fail because file doesn't exist at expected path) + g_mockRbusProperty.name = RRD_GET_PROFILE_EVENT; + rbusProperty_t mockGetProp = (rbusProperty_t)&g_mockRbusProperty; + + rbusError_t getResult = rrd_GetHandler(nullptr, mockGetProp, nullptr); + EXPECT_EQ(getResult, RBUS_ERROR_BUS_ERROR); // Expected since file doesn't exist +} + +TEST_F(RRDProfileHandlerTest, SetAndGetWorkflow_SpecificCategory) +{ + // Test complete workflow: set "System" -> get should return System category only + + // Step 1: Set profile category to specific category + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = "System"; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockSetProp = (rbusProperty_t)&g_mockRbusProperty; + rbusError_t setResult = rrd_SetHandler(nullptr, mockSetProp, nullptr); + + EXPECT_EQ(setResult, RBUS_ERROR_SUCCESS); + EXPECT_STREQ(RRDProfileCategory, "System"); + + // Step 2: Verify the category was persisted + // Clear global and reload from file + strcpy(RRDProfileCategory, "default"); + load_profile_category(); + EXPECT_STREQ(RRDProfileCategory, "System"); +} + +/* --------------- Boundary and stress tests --------------- */ + +TEST_F(RRDProfileHandlerTest, SetHandler_EmptyString) +{ + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = ""; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockProp = (rbusProperty_t)&mockRBusApi; + rbusError_t result = rrd_SetHandler(nullptr, mockProp, nullptr); + + EXPECT_EQ(result, RBUS_ERROR_SUCCESS); + EXPECT_STREQ(RRDProfileCategory, ""); +} + +TEST_F(RRDProfileHandlerTest, SetHandler_MaxLengthString) +{ + // Create a string of exactly 255 characters (max allowed) + std::string maxString(255, 'A'); + + g_mockRbusProperty.name = RRD_SET_PROFILE_EVENT; + g_mockRbusProperty.value = maxString; + g_mockRbusProperty.type = RBUS_STRING; + + rbusProperty_t mockProp = (rbusProperty_t)&g_mockRbusProperty; + rbusError_t result = rrd_SetHandler(nullptr, mockProp, nullptr); + + EXPECT_EQ(result, RBUS_ERROR_SUCCESS); + EXPECT_STREQ(RRDProfileCategory, maxString.c_str()); +} From 5c3c6be2b46a8d73e6e751edc858338277519ec5 Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Wed, 20 May 2026 10:21:46 +0530 Subject: [PATCH 4/6] Update rrdUnitTestRunner.cpp --- src/unittest/rrdUnitTestRunner.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index c7f09e9bd..964a05f4c 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1212,24 +1212,6 @@ TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMSGLengthNegative) free(buff.mdata); } -TEST_F(RRDRdmManagerDownloadRequestTest, HandlesParamStringAllocFail) -{ - // Simulate calloc failure by passing mSGLength=0 (should skip allocation) - issueNodeData issuestructNode; - issuestructNode.Node = strdup("MainNode"); - issuestructNode.subNode = strdup("SubNode"); - data_buf buff; - buff.mdata = strdup("ValidIssueTypeData"); - buff.jsonPath = strdup("UTJson/validJson.json"); - buff.inDynamic = false; - // Directly call with mSGLength=0 by passing NULL node - RRDRdmManagerDownloadRequest(NULL, buff.jsonPath, &buff, false); - free(issuestructNode.Node); - free(issuestructNode.subNode); - free(buff.jsonPath); - free(buff.mdata); -} - TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMsgDataStringAllocFail) { // Simulate msgDataString allocation fail by passing a very large node name From abf6c5ca509e24cc721d5f08b2b9c59747f0211a Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Wed, 20 May 2026 10:29:56 +0530 Subject: [PATCH 5/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/unittest/rrdUnitTestRunner.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 964a05f4c..813978287 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1215,10 +1215,12 @@ TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMSGLengthNegative) TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMsgDataStringAllocFail) { // Simulate msgDataString allocation fail by passing a very large node name + const size_t kLargeNodeSize = 1024 * 1024; issueNodeData issuestructNode; - issuestructNode.Node = (char*)malloc(1024*1024); - memset(issuestructNode.Node, 'A', 1024*1024-1); - issuestructNode.Node[1024*1024-1] = '\0'; + issuestructNode.Node = (char*)malloc(kLargeNodeSize); + ASSERT_NE(nullptr, issuestructNode.Node); + memset(issuestructNode.Node, 'A', kLargeNodeSize - 1); + issuestructNode.Node[kLargeNodeSize - 1] = '\0'; issuestructNode.subNode = strdup("SubNode"); data_buf buff; buff.mdata = strdup("ValidIssueTypeData"); From f1de7751ad4fffd133975aedc23cae2e81b0e8c0 Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Wed, 20 May 2026 10:33:11 +0530 Subject: [PATCH 6/6] Update rrdUnitTestRunner.cpp --- src/unittest/rrdUnitTestRunner.cpp | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 813978287..957b4b2eb 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -1212,28 +1212,6 @@ TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMSGLengthNegative) free(buff.mdata); } -TEST_F(RRDRdmManagerDownloadRequestTest, HandlesMsgDataStringAllocFail) -{ - // Simulate msgDataString allocation fail by passing a very large node name - const size_t kLargeNodeSize = 1024 * 1024; - issueNodeData issuestructNode; - issuestructNode.Node = (char*)malloc(kLargeNodeSize); - ASSERT_NE(nullptr, issuestructNode.Node); - memset(issuestructNode.Node, 'A', kLargeNodeSize - 1); - issuestructNode.Node[kLargeNodeSize - 1] = '\0'; - issuestructNode.subNode = strdup("SubNode"); - data_buf buff; - buff.mdata = strdup("ValidIssueTypeData"); - buff.jsonPath = strdup("UTJson/validJson.json"); - buff.inDynamic = false; - // This will likely fail to allocate msgDataString - RRDRdmManagerDownloadRequest(&issuestructNode, buff.jsonPath, &buff, false); - free(issuestructNode.Node); - free(issuestructNode.subNode); - free(buff.jsonPath); - free(buff.mdata); -} - TEST_F(RRDRdmManagerDownloadRequestTest, HandlesAppendModeTrue) { issueNodeData issuestructNode;