diff --git a/CHANGELOG.md b/CHANGELOG.md index 7399bcfc..3589a1d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,17 @@ 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.7.4](https://github.com/rdkcentral/telemetry/compare/1.7.3...1.7.4) + +- DELIA-69767: Replace popen() to prevent FD closures and improve logging [`#225`](https://github.com/rdkcentral/telemetry/pull/225) +- RDK-60072: Adding L1 unit test cases to improve code coverage [`#234`](https://github.com/rdkcentral/telemetry/pull/234) +- RDK-60072: Adding L1 unit test cases to improve code coverage [`#219`](https://github.com/rdkcentral/telemetry/pull/219) + #### [1.7.3](https://github.com/rdkcentral/telemetry/compare/1.7.2...1.7.3) +> 11 December 2025 + +- Changelog update for release 1.7.3 [`8a5cbfb`](https://github.com/rdkcentral/telemetry/commit/8a5cbfbec59dfe97c64ffb0547e309c65e4abd84) - RDKEMW-11275: Fix type conversion error in empty string fix [`893b4db`](https://github.com/rdkcentral/telemetry/commit/893b4dbc94bc5f6fcc1e8776edd6b6fb90ffc28e) - RDKEMW-11275: Fix type conversion error in empty string fix [`ec2291e`](https://github.com/rdkcentral/telemetry/commit/ec2291e68bdc2d7f298919cbe7e36a0c25f3dbe0) diff --git a/source/ccspinterface/rbusInterface.c b/source/ccspinterface/rbusInterface.c index d8cec2f7..a6a4c605 100644 --- a/source/ccspinterface/rbusInterface.c +++ b/source/ccspinterface/rbusInterface.c @@ -1953,3 +1953,10 @@ bool rbusCheckMethodExists(const char* rbusMethodName) rbusObject_Release(outParams); return true ; } +#ifdef GTEST_ENABLE +typedef void (*rbusReloadConfFunc)(rbusHandle_t, rbusEvent_t const *, rbusEventSubscription_t *); +rbusReloadConfFunc rbusReloadConfFuncCallback(void) +{ + return rbusReloadConf; +} +#endif diff --git a/source/commonlib/telemetry_busmessage_sender.c b/source/commonlib/telemetry_busmessage_sender.c index c5d43127..3734bc6c 100644 --- a/source/commonlib/telemetry_busmessage_sender.c +++ b/source/commonlib/telemetry_busmessage_sender.c @@ -55,7 +55,6 @@ static void *bus_handle = NULL; static bool isRFCT2Enable = false ; static bool isT2Ready = false; static bool isRbusEnabled = false ; -static int count = 0; static pthread_mutex_t initMtx = PTHREAD_MUTEX_INITIALIZER; static bool isMutexInitialized = false ; @@ -85,13 +84,23 @@ static void EVENT_DEBUG(char* format, ...) logHandle = fopen(SENDER_LOG_FILE, "a+"); if(logHandle) { - time_t rawtime; - struct tm* timeinfo; + struct timespec ts; + struct tm timeinfo; - time(&rawtime); - timeinfo = localtime(&rawtime); - static char timeBuffer[20] = { '\0' }; - strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", timeinfo); + if(clock_gettime(CLOCK_REALTIME, &ts) == -1) + { + fclose(logHandle); + pthread_mutex_unlock(&loggerMutex); + return; + } + + char timeBuffer[24] = { '\0' }; + long msecs; + + localtime_r(&ts.tv_sec, &timeinfo); + msecs = ts.tv_nsec / 1000000; + strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", &timeinfo); + snprintf(timeBuffer + strlen(timeBuffer), sizeof(timeBuffer) - strlen(timeBuffer), ".%03ld", msecs); fprintf(logHandle, "%s : ", timeBuffer); va_list argList; va_start(argList, format); @@ -316,8 +325,9 @@ void *cacheEventToFile(void *arg) fl.l_len = 0; fl.l_pid = 0; FILE *fs = NULL; - char path[100]; pthread_detach(pthread_self()); + int ch; + int count = 0; EVENT_ERROR("%s:%d, Caching the event to File\n", __func__, __LINE__); if(telemetry_data == NULL) { @@ -353,12 +363,25 @@ void *cacheEventToFile(void *arg) EVENT_ERROR("%s: File open error %s\n", __FUNCTION__, T2_CACHE_FILE); goto unlock; } - fs = popen ("cat /tmp/t2_caching_file | wc -l", "r"); - if(fs != NULL) + + fs = fopen(T2_CACHE_FILE, "r"); + if (fs != NULL) { - fgets(path, 100, fs); - count = atoi ( path ); - pclose(fs); + while ((ch = fgetc(fs)) != EOF) + { + if (ch == '\n') + { + count++; + } + } + + //If the file is not empty and does not contain a newline, call it one line + if (count == 0 && ftell(fs) > 0) + { + count++; + } + fclose(fs); + fs = NULL; } if(count < MAX_EVENT_CACHE) { diff --git a/source/dcautil/dca.c b/source/dcautil/dca.c index abbc8c78..616aa88c 100644 --- a/source/dcautil/dca.c +++ b/source/dcautil/dca.c @@ -1385,3 +1385,21 @@ int getDCAResultsInVector(GrepSeekProfile *gSeekProfile, Vector * vecMarkerList, T2Debug("%s --out \n", __FUNCTION__); return rc; } +#ifdef GTEST_ENABLE +typedef const char *(*strnstrFunc)(const char *, const char *, size_t); +strnstrFunc strnstrFuncCallback(void) +{ + return strnstr; +} +typedef time_t (*extractUnixTimestampFunc)(const char*, size_t); +extractUnixTimestampFunc extractUnixTimestampFuncCallback(void) +{ + return extractUnixTimestamp; +} + +typedef T2ERROR (*updateLogSeekFunc)(hash_map_t *, const char *, const long); +updateLogSeekFunc updateLogSeekFuncCallback(void) +{ + return updateLogSeek; +} +#endif diff --git a/source/protocol/http/curlinterface.c b/source/protocol/http/curlinterface.c index 98df178c..b5c0b805 100644 --- a/source/protocol/http/curlinterface.c +++ b/source/protocol/http/curlinterface.c @@ -633,3 +633,28 @@ T2ERROR sendCachedReportsOverHTTP(char *httpUrl, Vector *reportList) } return T2ERROR_SUCCESS; } + +#ifdef GTEST_ENABLE +typedef size_t (*WriteToFileFunc)(void *, size_t, size_t, void *); +typedef T2ERROR (*SetHeaderFunc)(CURL *, const char *, struct curl_slist **, childResponse *); +typedef T2ERROR (*SetMtlsHeadersFunc)(CURL *, const char *, const char *, childResponse *); +typedef T2ERROR (*SetPayloadFunc)(CURL *, const char *, childResponse *); +WriteToFileFunc getWriteToFileCallback() +{ + return writeToFile; +} + +SetHeaderFunc getSetHeaderCallback(void) +{ + return setHeader; +} + +SetMtlsHeadersFunc getSetMtlsHeadersCallback(void) +{ + return setMtlsHeaders; +} +SetPayloadFunc getSetPayloadCallback(void) +{ + return setPayload; +} +#endif diff --git a/source/reportgen/reportgen.c b/source/reportgen/reportgen.c index 88b47051..40ced998 100644 --- a/source/reportgen/reportgen.c +++ b/source/reportgen/reportgen.c @@ -1341,4 +1341,17 @@ void tagReportAsCached(char **jsonReport) destroyJSONReport(jsonReportObj); } +#ifdef GTEST_ENABLE +typedef bool (*checkForEmptyStringFunc)(char *); +typedef T2ERROR (*applyRegexToValueFunc)(char **, const char *); +checkForEmptyStringFunc checkForEmptyStringCallback(void) +{ + return checkForEmptyString; +} + +applyRegexToValueFunc applyRegexToValueCallback(void) +{ + return applyRegexToValue; +} +#endif diff --git a/source/scheduler/scheduler.c b/source/scheduler/scheduler.c index bab6fa6a..c6fdbf78 100644 --- a/source/scheduler/scheduler.c +++ b/source/scheduler/scheduler.c @@ -679,3 +679,10 @@ T2ERROR unregisterProfileFromScheduler(const char* profileName) T2Info("profile: %s, not found in scheduler. Already removed\n", profileName); return T2ERROR_FAILURE; } +#ifdef GTEST_ENABLE +typedef unsigned int (*getSchdInSec_fn)(char*); +getSchdInSec_fn getSchdInSec_fnCallback(void) +{ + return getSchdInSec; +} +#endif diff --git a/source/test/ccspinterface/ccspinterfaceTest.cpp b/source/test/ccspinterface/ccspinterfaceTest.cpp index 4bbca42c..8d640316 100755 --- a/source/test/ccspinterface/ccspinterfaceTest.cpp +++ b/source/test/ccspinterface/ccspinterfaceTest.cpp @@ -360,6 +360,66 @@ TEST_F(CcspInterfaceTest, getProfileParameterValues_ReturnsVector_1) { Vector_Destroy(profileValueList, freeProfileValues); } +TEST_F(CcspInterfaceTest, getProfileParameterValues_ReturnsVector_WithRbusGetExtError) { + Vector* paramlist = NULL; + Vector_Create(¶mlist); + Param* p = (Param*) malloc(sizeof(Param)); + p->name = strdup("Device.Dummy.Param"); + p->alias = strdup("Device.Dummy.Alias"); + p->paramType = strdup("dataModel"); + p->reportEmptyParam = true; + p->regexParam = NULL; + p->skipFreq = 0; + Param* p1 = (Param*) malloc(sizeof(Param)); + p1->name = strdup("Device.Dummy.Param"); + p1->alias = strdup("Device.Dummy.Alias"); + p1->paramType = strdup("dataModel"); + p1->reportEmptyParam = true; + p1->regexParam = NULL; + p1->skipFreq = 0; + Vector_PushBack(paramlist, p); + Vector_PushBack(paramlist, p1); + + EXPECT_CALL(*g_rbusMock, rbus_checkStatus()) + .Times(::testing::AtMost(1)) // Could be called multiple times depending on bus state + .WillRepeatedly(Return(RBUS_ENABLED)); + EXPECT_CALL(*g_rbusMock, rbus_registerLogHandler(_)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_SUCCESS)); + + EXPECT_CALL(*g_rbusMock, rbus_open(_, _)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_SUCCESS)); + + EXPECT_CALL(*g_rbusMock, rbus_getExt(_, _, _, _, _)) + .Times(::testing::AtMost(2)) + .WillRepeatedly(Return(RBUS_ERROR_BUS_ERROR)); + + Vector* profileValueList = getProfileParameterValues(paramlist, 2); + + EXPECT_NE(profileValueList, nullptr); + Vector_Destroy(paramlist, freeParam); + Vector_Destroy(profileValueList, freeProfileValues); +} + +TEST_F(CcspInterfaceTest, getProfileParameterValues_ReturnsFailure) { + + EXPECT_CALL(*g_rbusMock, rbus_checkStatus()) + .Times(::testing::AtMost(1)) // Could be called multiple times depending on bus state + .WillRepeatedly(Return(RBUS_ENABLED)); + EXPECT_CALL(*g_rbusMock, rbus_registerLogHandler(_)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_SUCCESS)); + + EXPECT_CALL(*g_rbusMock, rbus_open(_, _)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_BUS_ERROR)); + + Vector* profileValueList = getProfileParameterValues(NULL, 0); + + EXPECT_EQ(profileValueList, nullptr); +} + //Test the registerRbusT2EventListener function to register the rbus event listener TEST_F(CcspInterfaceTest, RegisterRbusT2EventListener_Succeeds) { @@ -2079,7 +2139,22 @@ TEST_F(CcspInterfaceTest, RbusMethodCaller_ReturnsSuccessForDummyMethod) { T2ERROR err = rbusMethodCaller((char*)"Dummy.Method", &inputParams, (char*)"payload", NULL); - EXPECT_TRUE(err == T2ERROR_SUCCESS || err == T2ERROR_FAILURE); // Accept either for stub + EXPECT_TRUE(err == T2ERROR_SUCCESS); +} + +TEST_F(CcspInterfaceTest, RbusMethodCaller_ReturnsFailureForDummyMethod) { + // Prepare dummy input params object + rbusObject_t inputParams; + EXPECT_CALL(*g_rbusMock, rbus_registerLogHandler(_)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_SUCCESS)); + + EXPECT_CALL(*g_rbusMock, rbus_open(_, _)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_BUS_ERROR)); + + T2ERROR err = rbusMethodCaller((char*)"Dummy.Method", &inputParams, (char*)"payload", NULL); + EXPECT_EQ(err, T2ERROR_FAILURE); } TEST_F(CcspInterfaceTest, RbusCheckMethodExists_ReturnsBool) { @@ -2111,6 +2186,51 @@ TEST_F(CcspInterfaceTest, RbusCheckMethodExists_ReturnsBool) { bool exists = rbusCheckMethodExists("Dummy.Method"); // Accept either true or false for stub - EXPECT_TRUE(exists == true || exists == false); + EXPECT_TRUE(exists); +} +TEST_F(CcspInterfaceTest, RbusCheckMethodExists_ReturnsBoolFalse){ + EXPECT_CALL(*g_rbusMock, rbus_registerLogHandler(_)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_SUCCESS)); + + EXPECT_CALL(*g_rbusMock, rbus_open(_, _)) + .Times(::testing::AtMost(1)) + .WillRepeatedly(Return(RBUS_ERROR_BUS_ERROR)); + bool exists = rbusCheckMethodExists("Dummy.Method"); + // Accept either true or false for stub + EXPECT_FALSE(exists); +} +#ifdef GTEST_ENABLE +extern "C" +{ +typedef void (*rbusReloadConfFunc)(rbusHandle_t, rbusEvent_t const *, rbusEventSubscription_t *); +rbusReloadConfFunc rbusReloadConfFuncCallback(void); } +TEST_F(CcspInterfaceTest, rbusReloadConf_StaticFunction_Coverage) { + // Retrieve function pointer to static function under test + rbusReloadConfFunc callback = rbusReloadConfFuncCallback(); + ASSERT_NE(callback, nullptr); + + // Prepare dummy arguments + rbusHandle_t mockHandle = nullptr; + + // Fill in the event and subscription structs according to your definition + rbusEvent_t event; + event.type = RBUS_EVENT_GENERAL; + event.name = "TestEvent"; + event.data = nullptr; + + rbusEventSubscription_t subscription; + subscription.eventName = "TestEvent"; + + // Normal call + callback(mockHandle, &event, &subscription); + + // NULL event - covers event == NULL branch + callback(mockHandle, nullptr, &subscription); + + // NULL subscription - covers subscription == NULL branch + callback(mockHandle, &event, nullptr); +} +#endif diff --git a/source/test/dcautils/dcautilTest.cpp b/source/test/dcautils/dcautilTest.cpp index 398f612e..43ab1afe 100644 --- a/source/test/dcautils/dcautilTest.cpp +++ b/source/test/dcautils/dcautilTest.cpp @@ -1630,4 +1630,90 @@ TEST_F(dcaTestFixture, getGrepResults_success) free(gsProfile); Vector_Destroy(vecMarkerList, freeGMarker); } +#ifdef GTEST_ENABLE +extern "C" { +typedef const char *(*strnstrFunc)(const char *, const char *, size_t); +strnstrFunc strnstrFuncCallback(void); +typedef time_t (*extractUnixTimestampFunc)(const char*, size_t); +extractUnixTimestampFunc extractUnixTimestampFuncCallback(void); +typedef T2ERROR (*updateLogSeekFunc)(hash_map_t *, const char *, const long); +updateLogSeekFunc updateLogSeekFuncCallback(void); +} +TEST(StaticStrnstrFunc, CoversMainBranches) +{ + auto fn = strnstrFuncCallback(); + ASSERT_NE(fn, nullptr); + + // NULL haystack or needle should return NULL + EXPECT_EQ(fn(NULL, "needle", 10), nullptr); + EXPECT_EQ(fn("haystack", NULL, 10), nullptr); + + // Empty needle returns haystack + const char* h1 = "haystack"; + EXPECT_EQ(fn(h1, "", 8), h1); + + // len < needle_len or overflow returns NULL + EXPECT_EQ(fn("foo", "foobar", 3), nullptr); + // May not always trigger overflow branch but included for completeness + // EXPECT_EQ(fn("foo", "foo", (size_t)-1), nullptr); + + // needle of length < 4 triggers simple search branch: found and not found + const char* h2 = "abcdef"; + EXPECT_EQ(fn(h2, "c", 6), h2 + 2); // found at position 2 + EXPECT_EQ(fn(h2, "e", 4), nullptr); // not found in first 4 chars + + // Optionally: COVER the optimized/longer path if you want (needle_len >= 4) + // This depends on your actual implementation for longer patterns +} +TEST(StaticExtractUnixTimestampFunc, CoversBranches) +{ + auto fn = extractUnixTimestampFuncCallback(); + ASSERT_NE(fn, nullptr); + + // NULL pointer or zero length hit early branch + EXPECT_EQ(fn(NULL, 12), (time_t)0); + EXPECT_EQ(fn("foo", 0), (time_t)0); + + // ISO 8601 - "2023-05-30T14:15:16.123 extra" + const char* iso = "2023-05-30T14:15:16.123 extra text"; + struct tm tm1 {}; + tm1.tm_year = 2023 - 1900; // years since 1900 + tm1.tm_mon = 5 - 1; // months since January + tm1.tm_mday = 30; + tm1.tm_hour = 14; + tm1.tm_min = 15; + tm1.tm_sec = 16; + time_t expected_iso = mktime(&tm1); + EXPECT_EQ(fn(iso, strlen(iso)), expected_iso); + + // YYMMDD-HH:MM:SS - "230530-14:15:16 something" + const char* yymmdd = "230530-14:15:16 something"; + struct tm tm2 {}; + tm2.tm_year = 2023 - 1900; + tm2.tm_mon = 5 - 1; + tm2.tm_mday = 30; + tm2.tm_hour = 14; + tm2.tm_min = 15; + tm2.tm_sec = 16; + time_t expected_yymmdd = mktime(&tm2); + EXPECT_EQ(fn(yymmdd, strlen(yymmdd)), expected_yymmdd); + + // Not matching format (should return 0) + EXPECT_EQ(fn("1656606000 extra text", strlen("1656606000 extra text")), (time_t)0); +} +TEST(StaticUpdateLogSeekFunc, CoversNullBranches) +{ + auto fn = updateLogSeekFuncCallback(); + ASSERT_NE(fn, nullptr); + // First branch: logSeekMap == NULL + EXPECT_EQ(fn(NULL, "dummy.log", 100), T2ERROR_FAILURE); + + // Second branch: logFileName == NULL + hash_map_t dummyMap; + EXPECT_EQ(fn(&dummyMap, NULL, 100), T2ERROR_FAILURE); + + // Additional test for a stub/empty map and filename to reach further code + // (Will continue past the NULL check; optionally extend to later logic in the function) +} +#endif diff --git a/source/test/protocol/ProtocolTest.cpp b/source/test/protocol/ProtocolTest.cpp index f66e1f48..1c28733d 100644 --- a/source/test/protocol/ProtocolTest.cpp +++ b/source/test/protocol/ProtocolTest.cpp @@ -43,6 +43,15 @@ typedef struct } childResponse ; +typedef size_t (*WriteToFileFunc)(void *, size_t, size_t, void *); +WriteToFileFunc getWriteToFileCallback(void); +typedef T2ERROR (*SetHeaderFunc)(CURL *, const char *, struct curl_slist **, childResponse *); +SetHeaderFunc getSetHeaderCallback(void); +typedef T2ERROR (*SetMtlsHeadersFunc)(CURL *, const char *, const char *, childResponse *); +SetMtlsHeadersFunc getSetMtlsHeadersCallback(void); +typedef T2ERROR (*SetPayloadFunc)(CURL *, const char *, childResponse *); +SetPayloadFunc getSetPayloadCallback(void); + } #include "gmock/gmock.h" @@ -480,3 +489,472 @@ TEST_F(protocolTestFixture, sendReportOverHTTP_6) EXPECT_EQ(T2ERROR_SUCCESS, sendReportOverHTTP(httpURL, payload,NULL)); Vector_Destroy(reportlist, free); } + +TEST_F(protocolTestFixture, sendCachedReportsOverHTTP_FailureCase) +{ + char *httpURL = "https://mockxconf:50051/dataLakeMock"; + Vector* reportList = NULL; + Vector_Create(&reportList); + + // Add two payloads to the report list + char* payload1 = strdup("This is payload 1"); + char* payload2 = strdup("This is payload 2"); + Vector_PushBack(reportList, payload1); + Vector_PushBack(reportList, payload2); + + // Mock failure for sendReportOverHTTP on the first payload + EXPECT_CALL(*g_fileIOMock, pipe(_)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_CALL(*m_xconfclientMock, isMtlsEnabled()) + .Times(1) + .WillOnce(Return(false)); + + EXPECT_CALL(*g_fileIOMock, fork()) + .Times(1) + .WillOnce(Return(-1)); + + // Ensure that the function returns a failure due to the mocked failure + EXPECT_EQ(T2ERROR_FAILURE, sendCachedReportsOverHTTP(httpURL, reportList)); + + // Clean up + Vector_Destroy(reportList, free); +} +#ifdef GTEST_ENABLE + // Unit test for static writeToFile via its function pointer + TEST(CURLINTERFACE_STATIC, WriteToFile) + { + WriteToFileFunc writeToFileCb = getWriteToFileCallback(); + ASSERT_NE(writeToFileCb, nullptr); + + // Prepare a buffer and write to a file + const char msg[] = "test fwrite"; + char testFile[] = "/tmp/unittest_writeToFileXXXXXX"; + int fd = mkstemp(testFile); + ASSERT_NE(fd, -1); + FILE* fp = fdopen(fd, "wb"); + ASSERT_NE(fp, nullptr); + + size_t n = writeToFileCb((void*)msg, sizeof(char), sizeof(msg), (void*)fp); + ASSERT_EQ(n, sizeof(msg)); + fclose(fp); + + // Now read back and validate + fp = fopen(testFile, "rb"); + ASSERT_NE(fp, nullptr); + char buf[32]; + size_t bytes = fread(buf, sizeof(char), sizeof(msg), fp); + ASSERT_EQ(bytes, sizeof(msg)); + ASSERT_EQ(memcmp(buf, msg, sizeof(msg)), 0); + fclose(fp); + remove(testFile); + } +TEST(CURLINTERFACE_STATIC, SetHeader) +{ + childResponse resp; + memset(&resp, 0, sizeof(resp)); + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + CURL *curl = nullptr; // purposely NULL + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + // According to implementation, curl==NULL returns T2ERROR_FAILURE + EXPECT_EQ(result, T2ERROR_FAILURE); +} +TEST(CURLINTERFACE_STATIC, SetMtlsHeaders_NULL) +{ + childResponse resp; + memset(&resp, 0, sizeof(resp)); + SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); + ASSERT_NE(cb, nullptr); + // NULL for CURL + EXPECT_EQ(cb(nullptr, "cert", "pwd", &resp), T2ERROR_FAILURE); + // NULL for certFile + EXPECT_EQ(cb((CURL*)0x1, nullptr, "pwd", &resp), T2ERROR_FAILURE); + // NULL for passwd + EXPECT_EQ(cb((CURL*)0x1, "cert", nullptr, &resp), T2ERROR_FAILURE); +} + +TEST(CURLINTERFACE_STATIC, SetPayload_NULL) +{ + childResponse resp; + memset(&resp, 0, sizeof(resp)); + SetPayloadFunc cb = getSetPayloadCallback(); + ASSERT_NE(cb, nullptr); + // NULL for CURL + EXPECT_EQ(cb(nullptr, "payload", &resp), T2ERROR_FAILURE); + // NULL for payload + EXPECT_EQ(cb((CURL*)0x1, nullptr, &resp), T2ERROR_FAILURE); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_NULL_DESTURL) +{ + childResponse resp; + memset(&resp, 0, sizeof(resp)); + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + struct curl_slist *headerList = nullptr; + + // destURL NULL should immediately fail + T2ERROR result = setHeaderCb(curl, NULL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_setopt_failure) +{ + childResponse resp; + memset(&resp, 0, sizeof(resp)); + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + + // Make the first curl_easy_setopt call fail to exercise early failure path. + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(1) + .WillOnce(Return(CURLE_FAILED_INIT)); + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + // curlSetopCode should be set to the failing CURLE code + EXPECT_EQ(resp.curlSetopCode, CURLE_FAILED_INIT); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_setopt_failure) +{ + SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); + ASSERT_NE(cb, nullptr); + + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Make the first curl_easy_setopt call fail and ensure function returns failure + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(1) + .WillOnce(Return(CURLE_UNKNOWN_OPTION)); + + T2ERROR result = cb((CURL*)0x1, "dummyCert", "dummyPwd", &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_UNKNOWN_OPTION); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetPayload_setopt_failure) +{ + SetPayloadFunc cb = getSetPayloadCallback(); + ASSERT_NE(cb, nullptr); + + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Make CURLOPT_POSTFIELDS setopt call fail + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(1) + .WillOnce(Return(CURLE_OUT_OF_MEMORY)); + + T2ERROR result = cb((CURL*)0x1, "payload", &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_OUT_OF_MEMORY); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_SSLVERSION_failure) +{ + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // First setopt (CURLOPT_URL) succeeds, second (CURLOPT_SSLVERSION) fails. + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(2) + .WillOnce(Return(CURLE_OK)) + .WillOnce(Return(CURLE_SSL_CONNECT_ERROR)); + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_SSL_CONNECT_ERROR); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_CUSTOMREQUEST_failure) +{ + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Sequence: CURLOPT_URL -> OK, CURLOPT_SSLVERSION -> OK, CURLOPT_CUSTOMREQUEST -> fail + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(3) + .WillOnce(Return(CURLE_OK)) // CURLOPT_URL + .WillOnce(Return(CURLE_OK)) // CURLOPT_SSLVERSION + .WillOnce(Return(CURLE_UNKNOWN_OPTION)); // CURLOPT_CUSTOMREQUEST fails + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_UNKNOWN_OPTION); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_TIMEOUT_failure) +{ + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Sequence: CURLOPT_URL -> OK, CURLOPT_SSLVERSION -> OK, CURLOPT_CUSTOMREQUEST -> OK, CURLOPT_TIMEOUT -> fail + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(4) + .WillOnce(Return(CURLE_OK)) // CURLOPT_URL + .WillOnce(Return(CURLE_OK)) // CURLOPT_SSLVERSION + .WillOnce(Return(CURLE_OK)) // CURLOPT_CUSTOMREQUEST + .WillOnce(Return(CURLE_OUT_OF_MEMORY)); // CURLOPT_TIMEOUT fails + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_OUT_OF_MEMORY); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_HTTPHEADER_failure) +{ + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Ensure curl_slist_append calls return a valid non-null list pointer + EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) + .Times(2) + .WillRepeatedly(Return((struct curl_slist*)0x1)); + + // Sequence of curl_easy_setopt_mock returns: + // URL, SSLVERSION, CUSTOMREQUEST, TIMEOUT, (optional INTERFACE), HTTPHEADER -> fail + // Use 6 returns: first 5 OK, 6th is the failing HTTPHEADER setopt. + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(6) + .WillOnce(Return(CURLE_OK)) // CURLOPT_URL + .WillOnce(Return(CURLE_OK)) // CURLOPT_SSLVERSION + .WillOnce(Return(CURLE_OK)) // CURLOPT_CUSTOMREQUEST + .WillOnce(Return(CURLE_OK)) // CURLOPT_TIMEOUT + .WillOnce(Return(CURLE_OK)) // CURLOPT_INTERFACE or extra opt (if present) + .WillOnce(Return(CURLE_COULDNT_CONNECT)); // CURLOPT_HTTPHEADER fails + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_COULDNT_CONNECT); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_WRITEFUNCTION_failure) +{ + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Ensure curl_slist_append calls return a valid non-null list pointer + EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) + .Times(2) + .WillRepeatedly(Return((struct curl_slist*)0x1)); + + // Make CURLOPT_WRITEFUNCTION fail specifically (robust to optional extra setopt calls). + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_, CURLOPT_WRITEFUNCTION, _)) + .WillOnce(Return(CURLE_SEND_ERROR)); + + // All other curl_easy_setopt_mock invocations should succeed. + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_, ::testing::Ne(CURLOPT_WRITEFUNCTION), _)) + .WillRepeatedly(Return(CURLE_OK)); + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_SEND_ERROR); +} +/* New tests to cover success paths for static helpers in curlinterface.c */ +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetPayload_SUCCESS) +{ + SetPayloadFunc cb = getSetPayloadCallback(); + ASSERT_NE(cb, nullptr); + childResponse resp; + // Allow any curl_easy_setopt_mock calls and return CURLE_OK + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(::testing::AtLeast(2)) + .WillRepeatedly(Return(CURLE_OK)); + T2ERROR res = cb((CURL*)0x1, "dummy-payload", &resp); + EXPECT_EQ(res, T2ERROR_SUCCESS); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_SSLCERT_failure) +{ + SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); + ASSERT_NE(cb, nullptr); + + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + CURL *curl = (CURL*)0x1; + const char* certFile = "/tmp/mycert.p12"; + const char* passwd = "dummyPwd"; + + // We need to return CURLE_OK for CURLOPT_SSLCERTTYPE, + // And return error for CURLOPT_SSLCERT only, then do not expect calls for subsequent steps + { + ::testing::InSequence seq; + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERTTYPE, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERT, ::testing::_)) + .WillOnce(Return(CURLE_SSL_CERTPROBLEM)); + // No further setopt calls expected after error + } + + T2ERROR result = cb(curl, certFile, passwd, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_SSL_CERTPROBLEM); + // Optionally: check that lineNumber is set to a value after the failing line +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_KEYPASSWD_failure) +{ + SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); + ASSERT_NE(cb, nullptr); + + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + CURL *curl = (CURL*)0x1; + const char* certFile = "/tmp/mycert.p12"; + const char* passwd = "dummyPwd"; + + { + ::testing::InSequence seq; + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERTTYPE, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERT, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_KEYPASSWD, ::testing::_)) + .WillOnce(Return(CURLE_LOGIN_DENIED)); + // No further setopt calls expected + } + + T2ERROR result = cb(curl, certFile, passwd, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_LOGIN_DENIED); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_SSLVERIFYPEER_failure) +{ + SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); + ASSERT_NE(cb, nullptr); + + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + CURL *curl = (CURL*)0x1; + const char* certFile = "/tmp/mycert.p12"; + const char* passwd = "dummyPwd"; + + { + ::testing::InSequence seq; + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERTTYPE, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSLCERT, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_KEYPASSWD, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_SSL_VERIFYPEER, ::testing::_)) + .WillOnce(Return(CURLE_PEER_FAILED_VERIFICATION)); + } + + T2ERROR result = cb(curl, certFile, passwd, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_PEER_FAILED_VERIFICATION); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetPayload_POSTFIELDSIZE_failure) +{ + SetPayloadFunc cb = getSetPayloadCallback(); + ASSERT_NE(cb, nullptr); + + childResponse resp; + memset(&resp, 0, sizeof(resp)); + CURL *curl = (CURL*)0x1; + const char *payload = "payload-for-testing"; + + { + ::testing::InSequence seq; + // First call(s) for setting postfields may succeed + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_POSTFIELDS, ::testing::_)) + .WillOnce(Return(CURLE_OK)); + // We force failure on POSTFIELDSIZE + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(curl, CURLOPT_POSTFIELDSIZE, ::testing::_)) + .WillOnce(Return(CURLE_WRITE_ERROR)); + } + + T2ERROR result = cb(curl, payload, &resp); + EXPECT_EQ(result, T2ERROR_FAILURE); + EXPECT_EQ(resp.curlSetopCode, CURLE_WRITE_ERROR); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetMtlsHeaders_SUCCESS) +{ + SetMtlsHeadersFunc cb = getSetMtlsHeadersCallback(); + ASSERT_NE(cb, nullptr); + childResponse resp; + // Expect at least 4 curl_easy_setopt calls for the mtls settings + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(::testing::AtLeast(4)) + .WillRepeatedly(Return(CURLE_OK)); + T2ERROR res = cb((CURL*)0x1, "/tmp/cert.p12", "passwd", &resp); + EXPECT_EQ(res, T2ERROR_SUCCESS); +} + +TEST_F(protocolTestFixture, CURLINTERFACE_STATIC_SetHeader_SUCCESS) +{ + SetHeaderFunc setHeaderCb = getSetHeaderCallback(); + ASSERT_NE(setHeaderCb, nullptr); + + CURL *curl = (CURL*)0x1; + const char *destURL = "http://localhost"; + struct curl_slist *headerList = nullptr; + childResponse resp; + memset(&resp, 0, sizeof(resp)); + + // Expect curl_slist_append to be called twice for the two headers and return a non-null list + EXPECT_CALL(*g_fileIOMock, curl_slist_append(_, _)) + .Times(2) + .WillRepeatedly(Return((struct curl_slist*)0x1)); + + // Allow the curl_easy_setopt_mock calls that setHeader performs and return CURLE_OK. + // Use AtLeast to be resilient to small differences in option counts due to build flags. + EXPECT_CALL(*g_fileIOMock, curl_easy_setopt_mock(_,_,_)) + .Times(::testing::AtLeast(6)) + .WillRepeatedly(Return(CURLE_OK)); + + T2ERROR result = setHeaderCb(curl, destURL, &headerList, &resp); + EXPECT_EQ(result, T2ERROR_SUCCESS); + // headerList should have been set by curl_slist_append to a non-null pointer + EXPECT_NE(headerList, nullptr); +} +#endif diff --git a/source/test/reportgen/reportgenMock.cpp b/source/test/reportgen/reportgenMock.cpp index c6d203d0..bbc32945 100644 --- a/source/test/reportgen/reportgenMock.cpp +++ b/source/test/reportgen/reportgenMock.cpp @@ -230,4 +230,31 @@ extern "C" T2ERROR getParameterValue(const char* paramName, char **paramValue) return T2ERROR_FAILURE; } return m_reportgenMock->getParameterValue(paramName, paramValue); + +} +extern "C" T2ERROR applyRegexToValue(char **value, const char *regex) +{ + if (!m_reportgenMock) + return T2ERROR_FAILURE; + return m_reportgenMock->applyRegexToValue(value, regex); +} +extern "C" int cJSON_IsArray(const cJSON* arr) +{ + if (!m_reportgenMock) + return 0; + return m_reportgenMock->cJSON_IsArray(arr); +} + +extern "C" cJSON_bool cJSON_InsertItemInArray(cJSON* array, int which, cJSON* item) +{ + if (!m_reportgenMock) + return 0; + return m_reportgenMock->cJSON_InsertItemInArray(array, which, item); +} + +extern "C" cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + if (!m_reportgenMock) + return nullptr; + return m_reportgenMock->cJSON_GetObjectItemCaseSensitive(object, string); } diff --git a/source/test/reportgen/reportgenMock.h b/source/test/reportgen/reportgenMock.h index 29f971c6..f35d683d 100644 --- a/source/test/reportgen/reportgenMock.h +++ b/source/test/reportgen/reportgenMock.h @@ -55,6 +55,10 @@ class ReportgenInterface virtual char* curl_easy_escape(CURL *c, const char *string, int len) = 0; virtual void curl_free(void *ptr) = 0; virtual void curl_easy_cleanup(CURL *curl) = 0; + virtual T2ERROR applyRegexToValue(char **value, const char *regex) = 0; + virtual int cJSON_IsArray(const cJSON* arr) = 0; + virtual cJSON_bool cJSON_InsertItemInArray(cJSON* array, int which, cJSON* item) = 0; + virtual cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) = 0; }; class ReportgenMock: public ReportgenInterface @@ -84,6 +88,10 @@ class ReportgenMock: public ReportgenInterface MOCK_METHOD3(curl_easy_escape, char*(CURL *, const char *, int)); MOCK_METHOD1(curl_free, void(void *)); MOCK_METHOD1(curl_easy_cleanup, void(CURL *)); + MOCK_METHOD2(applyRegexToValue, T2ERROR(char **, const char *)); + MOCK_METHOD1(cJSON_IsArray, int(const cJSON*)); + MOCK_METHOD3(cJSON_InsertItemInArray, cJSON_bool(cJSON*, int, cJSON*)); + MOCK_METHOD2(cJSON_GetObjectItemCaseSensitive, cJSON * (const cJSON * const, const char * const)); }; #endif diff --git a/source/test/reportgen/reportgenTest.cpp b/source/test/reportgen/reportgenTest.cpp index dd727ce3..4d807a85 100644 --- a/source/test/reportgen/reportgenTest.cpp +++ b/source/test/reportgen/reportgenTest.cpp @@ -47,6 +47,11 @@ using ::testing::StrEq; rdklogMock *m_rdklogMock = NULL; +extern "C" +{ + void convertVectorToJson(cJSON *output, Vector *input); +} + class rdklogTestFixture : public ::testing::Test { protected: rdklogMock rdklogmock_IO; @@ -78,6 +83,20 @@ class rdklogTestFixture : public ::testing::Test { printf("%s\n", __func__); } }; +void FreeString(void* item) { free(item); } + +TEST(ConvertVectorToJson, OutputNotNull_VectorEmpty) +{ + cJSON *output = cJSON_CreateArray(); + Vector *input = nullptr; + ASSERT_EQ(Vector_Create(&input), T2ERROR_SUCCESS); + + convertVectorToJson(output, input); + EXPECT_EQ(cJSON_GetArraySize(output), 0); + + Vector_Destroy(input, FreeString); + cJSON_Delete(output); +} TEST(DESTROY_JSONREPORT, CHECK_JSON) { @@ -242,6 +261,124 @@ TEST_F(reportgenTestFixture, PrepareHttpUrl) free(data); } +TEST_F(reportgenTestFixture, prepareHttpUrl_CurlInitFails) { + T2HTTP httpStruct; + T2HTTP* http = &httpStruct; + + // Simulate curl_easy_init() failing + EXPECT_CALL(*m_reportgenMock, curl_easy_init()) + .WillOnce(::testing::Return(nullptr)); + + // Optionally: you could check for error logging if T2Error is mockable + + // Call function under test + char* result = prepareHttpUrl(http); + + // Expect NULL return + EXPECT_EQ(result, nullptr); +} + +TEST_F(reportgenTestFixture, prepareHttpUrl_getParameterValueFailsAndEmptyValue) { + T2HTTP httpStruct = {0}; + HTTPReqParam httpParam1 = {0}; + HTTPReqParam httpParam2 = {0}; + Vector* paramList = NULL; + Vector_Create(¶mList); + + // Use mutable char arrays or cast string literals to char* + char paramRefFail[] = "Test.Fail"; + char paramRefEmpty[] = "Test.Empty"; + char paramName[] = "testParam"; + char url[] = "http://rdk.central"; + httpStruct.URL = url; + httpStruct.RequestURIparamList = paramList; + + httpParam1.HttpName = paramName; + httpParam1.HttpValue = nullptr; + httpParam1.HttpRef = paramRefFail; + httpParam2.HttpName = paramName; + httpParam2.HttpValue = nullptr; + httpParam2.HttpRef = paramRefEmpty; + + Vector_PushBack(paramList, &httpParam1); + Vector_PushBack(paramList, &httpParam2); + + EXPECT_CALL(*m_reportgenMock, curl_easy_init()) + .WillOnce(::testing::Return(reinterpret_cast(0x1))); + EXPECT_CALL(*m_reportgenMock, getParameterValue(::testing::StrEq(paramRefFail), ::testing::_)) + .WillOnce(::testing::DoAll( + ::testing::SetArgPointee<1>(nullptr), + ::testing::Return(T2ERROR_FAILURE) + )); + char* emptyValue = strdup(""); // allocate so code under test can free() it + EXPECT_CALL(*m_reportgenMock, getParameterValue(::testing::StrEq(paramRefEmpty), ::testing::_)) + .WillOnce(::testing::DoAll( + ::testing::SetArgPointee<1>(emptyValue), + ::testing::Return(T2ERROR_SUCCESS) + )); + EXPECT_CALL(*m_reportgenMock, curl_easy_cleanup(reinterpret_cast(0x1))).Times(1); + + char* result = prepareHttpUrl(&httpStruct); + + ASSERT_NE(result, nullptr); + EXPECT_STREQ(result, url); // No new params on fail/empty + free(result); + + Vector_Destroy(paramList,nullptr); +} + +// This tests the branch: getParameterValue returns success, paramValue[0] != '\0', curl_easy_escape is called, paramValue is freed + +TEST_F(reportgenTestFixture, prepareHttpUrl_ParamValueIsEscapedAndFreed) { + // Arrange + T2HTTP httpStruct = {0}; + HTTPReqParam httpParam = {0}; + Vector* paramList = NULL; + Vector_Create(¶mList); + + char paramRef[] = "Test.Param"; + char paramName[] = "testParam"; + char url[] = "http://rdk.central"; + httpStruct.URL = url; + httpStruct.RequestURIparamList = paramList; + + httpParam.HttpName = paramName; + httpParam.HttpValue = nullptr; + httpParam.HttpRef = paramRef; + Vector_PushBack(paramList, &httpParam); + + // Returned param value (will be freed by production code) + char* paramValue = strdup("value123"); + + // Escaped URI string (will be freed by curl_free) + char escapedValue[] = "escapedValue"; + + EXPECT_CALL(*m_reportgenMock, curl_easy_init()) + .WillOnce(::testing::Return(reinterpret_cast(0x1))); + EXPECT_CALL(*m_reportgenMock, getParameterValue(::testing::StrEq(paramRef), ::testing::_)) + .WillOnce(::testing::DoAll( + ::testing::SetArgPointee<1>(paramValue), + ::testing::Return(T2ERROR_SUCCESS) + )); + EXPECT_CALL(*m_reportgenMock, curl_easy_escape(reinterpret_cast(0x1), ::testing::StrEq("value123"), 0)) + .WillOnce(::testing::Return(escapedValue)); + // Code under test is expected to call free(paramValue) + // No need to explicitly mock free if not using a custom allocator + EXPECT_CALL(*m_reportgenMock, curl_free(escapedValue)).Times(1); + EXPECT_CALL(*m_reportgenMock, curl_easy_cleanup(reinterpret_cast(0x1))).Times(1); + + // Act + char* result = prepareHttpUrl(&httpStruct); + + // Assert: URL now includes parameter (escaped value) + ASSERT_NE(result, nullptr); + std::string expectedStart = std::string(url) + "?" + paramName + "=escapedValue"; + EXPECT_EQ(0, strncmp(result, expectedStart.c_str(), expectedStart.size())); + free(result); + + Vector_Destroy(paramList, NULL); +} + TEST_F(reportgenTestFixture, PrepareJSONReport1) { cJSON* jsonobj = (cJSON*)malloc(sizeof(cJSON)); @@ -434,7 +571,7 @@ TEST_F(reportgenTestFixture, encodeGrepResultInJSON2) TEST_F(reportgenTestFixture, encodeGrepResultInJSON3) { Vector* grepResult = NULL; - Vector_Create(&grepResult); + Vector_Create(&grepResult); GrepMarker* gparam = (GrepMarker *) malloc(sizeof(GrepMarker)); memset(gparam, 0, sizeof(GrepMarker)); gparam->markerName = strdup("TEST_MARKER1"); @@ -475,7 +612,116 @@ TEST_F(reportgenTestFixture, encodeGrepResultInJSON3) Vector_Destroy(grepResult, freeGResult); } -//When ParamValueCount is 0 +TEST_F(reportgenTestFixture, encodeGrepResultInJSON4) +{ + // Case 1: both NULL + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeGrepResultInJSON(NULL, NULL)); + + // Case 2: valArray is NULL, grepMarkerList non-NULL + Vector* grepMarkerList = nullptr; + ASSERT_EQ(Vector_Create(&grepMarkerList), T2ERROR_SUCCESS); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeGrepResultInJSON(NULL, grepMarkerList)); + + // Case 3: valArray non-NULL, grepMarkerList is NULL + cJSON* valArray = cJSON_CreateArray(); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeGrepResultInJSON(valArray, NULL)); + + // cleanup + Vector_Destroy(grepMarkerList, free); // assuming it only contains malloc'd pointers + cJSON_Delete(valArray); +} +TEST_F(reportgenTestFixture, encodeGrepResultInJSON_CreateObjectFails) +{ + Vector* grepList = nullptr; + ASSERT_EQ(Vector_Create(&grepList), T2ERROR_SUCCESS); + + GrepMarker* marker = (GrepMarker*)malloc(sizeof(GrepMarker)); + memset(marker, 0, sizeof(GrepMarker)); + marker->mType = MTYPE_ACCUMULATE; + Vector_Create(&marker->u.accumulatedValues); + Vector_PushBack(marker->u.accumulatedValues, strdup("ANYVAL")); + Vector_PushBack(grepList, marker); + + cJSON* valArray = (cJSON*)0xdeadbeef; // any fake handle + + // Failure: CreateObject returns NULL triggers return at 507 + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .Times(1) + .WillOnce(::testing::Return(nullptr)); + + EXPECT_EQ(T2ERROR_FAILURE, encodeGrepResultInJSON(valArray, grepList)); + + free(Vector_At(marker->u.accumulatedValues, 0)); + Vector_Destroy(marker->u.accumulatedValues, nullptr); + free(marker); + Vector_Destroy(grepList, nullptr); +} + +TEST_F(reportgenTestFixture, encodeGrepResultInJSON_CreateArrayFails) +{ + Vector* grepList = nullptr; + ASSERT_EQ(Vector_Create(&grepList), T2ERROR_SUCCESS); + + GrepMarker* marker = (GrepMarker*)malloc(sizeof(GrepMarker)); + memset(marker, 0, sizeof(GrepMarker)); + marker->mType = MTYPE_ACCUMULATE; + Vector_Create(&marker->u.accumulatedValues); + Vector_PushBack(marker->u.accumulatedValues, strdup("ANYVAL")); + Vector_PushBack(grepList, marker); + + cJSON* valArray = (cJSON*)0xdeadbeef; + cJSON* mockArrayObj = (cJSON*)0x1234; + + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .Times(1) + .WillOnce(::testing::Return(mockArrayObj)); + EXPECT_CALL(*m_reportgenMock, cJSON_CreateArray()) + .Times(1) + .WillOnce(::testing::Return(nullptr)); + EXPECT_CALL(*m_reportgenMock, cJSON_Delete(mockArrayObj)) + .Times(1); + + EXPECT_EQ(T2ERROR_FAILURE, encodeGrepResultInJSON(valArray, grepList)); + + free(Vector_At(marker->u.accumulatedValues, 0)); + Vector_Destroy(marker->u.accumulatedValues, nullptr); + free(marker); + Vector_Destroy(grepList, nullptr); +} +TEST_F(reportgenTestFixture, encodeGrepResultInJSON_null_marker_skipped) { + Vector *list = nullptr; + ASSERT_EQ(Vector_Create(&list), T2ERROR_SUCCESS); + Vector_PushBack(list, nullptr); // Intentionally NULL marker + cJSON *valArray = (cJSON*)malloc(sizeof(cJSON)); + EXPECT_EQ(T2ERROR_SUCCESS, encodeGrepResultInJSON(valArray, list)); + Vector_Destroy(list, nullptr); + free(valArray); +} +TEST_F(reportgenTestFixture, encodeGrepResultInJSON_counter_regex_all_branches) { + Vector* list = NULL; + ASSERT_EQ(Vector_Create(&list), T2ERROR_SUCCESS); + GrepMarker* marker = (GrepMarker*)malloc(sizeof(GrepMarker)); + memset(marker, 0, sizeof(GrepMarker)); + marker->mType = MTYPE_COUNTER; + marker->u.count = 2; + marker->markerName = strdup("mark1"); + marker->regexParam = strdup("[0-9]"); + marker->trimParam = true; + Vector_PushBack(list, marker); + cJSON* valArray = (cJSON*)malloc(sizeof(cJSON)); + cJSON* obj = (cJSON*)0xa10; + // Object creation + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()).WillOnce(Return(obj)); + // String add fails, to test error path + EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject(obj, _, _)).WillOnce(Return(nullptr)); + EXPECT_CALL(*m_reportgenMock, cJSON_Delete(obj)).Times(1); + EXPECT_EQ(T2ERROR_FAILURE, encodeGrepResultInJSON(valArray, list)); + free(marker->markerName); + free(marker->regexParam); + free(marker); + Vector_Destroy(list, nullptr); + free(valArray); +} TEST_F(reportgenTestFixture, encodeParamResultInJSON) { Vector *paramNameList = NULL; @@ -514,7 +760,6 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON) Vector_Destroy(paramValueList, freeProfileValues); } -//When paramValcount is 1 TEST_F(reportgenTestFixture, encodeParamResultInJSON1) { Vector *paramNameList = NULL; @@ -885,6 +1130,7 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON8) Vector_Destroy(paramNameList, freeParam); Vector_Destroy(paramValueList, freeProfileValues); } + /* TEST_F(reportgenTestFixture, encodeParamResultInJSON10) { @@ -968,6 +1214,24 @@ TEST_F(reportgenTestFixture, encodeParamResultInJSON10) Vector_Destroy(paramValueList, freeProfileValues); } */ +TEST_F(reportgenTestFixture, encodeEventMarkersInJSON_null_args) +{ + Vector *eventMarkerList = NULL; + cJSON *valArray = NULL; + + // Test: Both NULL + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeEventMarkersInJSON(NULL, NULL)); + + // Test: valArray NULL, eventMarkerList non-NULL + Vector_Create(&eventMarkerList); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeEventMarkersInJSON(NULL, eventMarkerList)); + Vector_Destroy(eventMarkerList, nullptr); + + // Test: valArray non-NULL, eventMarkerList NULL + valArray = (cJSON*)malloc(sizeof(cJSON)); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeEventMarkersInJSON(valArray, NULL)); + free(valArray); +} TEST_F(reportgenTestFixture, encodeEventMarkersInJSON) { cJSON* valArray = NULL; @@ -1144,6 +1408,7 @@ TEST_F(reportgenTestFixture, encodeEventMarkersInJSON4) } + TEST_F(reportgenTestFixture, encodeEventMarkersInJSON5) { cJSON* valArray = NULL; @@ -1413,4 +1678,364 @@ TEST_F(reportgenTestFixture, encodeEventMarkersInJSON10) } Vector_Destroy(eventMarkerList, freeEMarker); } +TEST_F(reportgenTestFixture, encodeTopResultInJSON_null_args) +{ + Vector *topResultList = NULL; + cJSON *valArray = NULL; + + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeTopResultInJSON(NULL, NULL)); + + Vector_Create(&topResultList); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeTopResultInJSON(NULL, topResultList)); + Vector_Destroy(topResultList, nullptr); + + valArray = (cJSON*)malloc(sizeof(cJSON)); + EXPECT_EQ(T2ERROR_INVALID_ARGS, encodeTopResultInJSON(valArray, NULL)); + free(valArray); +} +TEST_F(reportgenTestFixture, encodeTopResultInJSON_loadAverage_success) +{ + Vector* topMarkerList = nullptr; + ASSERT_EQ(Vector_Create(&topMarkerList), T2ERROR_SUCCESS); + + TopMarker* marker = (TopMarker*)calloc(1, sizeof(TopMarker)); + marker->markerName = strdup("load_marker"); + marker->searchString = strdup("mysearch"); + marker->loadAverage = strdup(" 1.23 "); + marker->trimParam = true; + marker->regexParam = nullptr; // No regex branch + + Vector_PushBack(topMarkerList, marker); + + cJSON* valArray = (cJSON*)malloc(sizeof(cJSON)); + cJSON* mockObj = (cJSON*)0x12345; + + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .Times(1).WillOnce(Return(mockObj)); + EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject(mockObj, StrEq("load_marker"), StrEq("1.23"))) + .Times(1) + .WillOnce(Return((cJSON*) 0xDEADBEEF)); + EXPECT_CALL(*m_reportgenMock, cJSON_AddItemToArray(valArray, mockObj)) + .Times(1).WillOnce(Return(true)); + + EXPECT_EQ(T2ERROR_SUCCESS, encodeTopResultInJSON(valArray, topMarkerList)); + free(marker->markerName); + free(marker->searchString); + free(marker->loadAverage); + free(marker); + Vector_Destroy(topMarkerList, nullptr); + if(valArray) free(valArray); +} +TEST_F(reportgenTestFixture, encodeTopResultInJSON_loadAverage_cjsonCreateObjectFail) +{ + Vector* topMarkerList = nullptr; + ASSERT_EQ(Vector_Create(&topMarkerList), T2ERROR_SUCCESS); + + TopMarker* marker = (TopMarker*)calloc(1, sizeof(TopMarker)); + marker->markerName = strdup("load_marker"); + marker->searchString = strdup("mysearch"); + marker->loadAverage = strdup("test"); + Vector_PushBack(topMarkerList, marker); + + cJSON* valArray = (cJSON*)malloc(sizeof(cJSON)); + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()).Times(1).WillOnce(Return((cJSON*)NULL)); + EXPECT_EQ(T2ERROR_FAILURE, encodeTopResultInJSON(valArray, topMarkerList)); + + free(marker->markerName); + free(marker->searchString); + free(marker->loadAverage); + free(marker); + Vector_Destroy(topMarkerList, nullptr); + if(valArray) free(valArray); +} +TEST_F(reportgenTestFixture, encodeTopResultInJSON_otherwise_no_values) +{ + Vector* topMarkerList = nullptr; + ASSERT_EQ(Vector_Create(&topMarkerList), T2ERROR_SUCCESS); + + TopMarker* marker = (TopMarker*)calloc(1, sizeof(TopMarker)); + marker->searchString = strdup("sys_slot"); + Vector_PushBack(topMarkerList, marker); + + cJSON* valArray = (cJSON*)malloc(sizeof(cJSON)); + + EXPECT_EQ(T2ERROR_SUCCESS, encodeTopResultInJSON(valArray, topMarkerList)); + + free(marker->searchString); + free(marker); + Vector_Destroy(topMarkerList, nullptr); + if(valArray) free(valArray); +} +TEST_F(reportgenTestFixture, encodeTopResultInJSON_nullTopMarker_skip) +{ + Vector* topMarkerList = nullptr; + ASSERT_EQ(Vector_Create(&topMarkerList), T2ERROR_SUCCESS); + + TopMarker* marker = nullptr; + Vector_PushBack(topMarkerList, marker); // actually NULL + + cJSON* valArray = (cJSON*)malloc(sizeof(cJSON)); + + EXPECT_EQ(T2ERROR_SUCCESS, encodeTopResultInJSON(valArray, topMarkerList)); + + Vector_Destroy(topMarkerList, nullptr); + if(valArray) free(valArray); +} +TEST_F(reportgenTestFixture, encodeTopResultInJSON_cpuMem_cjsonCreateObjectFail) +{ + Vector* topMarkerList = nullptr; + ASSERT_EQ(Vector_Create(&topMarkerList), T2ERROR_SUCCESS); + + TopMarker* marker = (TopMarker*)calloc(1, sizeof(TopMarker)); + marker->searchString = strdup("mysys"); + marker->cpuValue = strdup("81"); + marker->memValue = strdup("7"); + marker->loadAverage = nullptr; // Not loadAverage path + + Vector_PushBack(topMarkerList, marker); + + cJSON* valArray = (cJSON*)malloc(sizeof(cJSON)); + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .Times(1) + .WillOnce(Return((cJSON*)NULL)); + + EXPECT_EQ(T2ERROR_FAILURE, encodeTopResultInJSON(valArray, topMarkerList)); + + // Cleanup + free(marker->searchString); + free(marker->cpuValue); + free(marker->memValue); + free(marker); + Vector_Destroy(topMarkerList, nullptr); + if(valArray) free(valArray); +} +extern "C" void tagReportAsCached(char **jsonReport); + +TEST_F(reportgenTestFixture, tagReportAsCached_JSONReport_NULL) +{ + tagReportAsCached(NULL); +} + +TEST_F(reportgenTestFixture, tagReportAsCached_InvalidJSON) +{ + char* fakeJson = strdup("{notvalidjson: }"); + EXPECT_CALL(*m_reportgenMock, cJSON_Parse(_)) + .Times(1) + .WillOnce(Return(nullptr)); + tagReportAsCached(&fakeJson); + // Should not crash or free/rewrite the pointer if parsing fails + free(fakeJson); +} +TEST_F(reportgenTestFixture, tagReportAsCached_AddToSearchResult) +{ + char* fakeJson = strdup("{\"searchResult\":[]}"); + cJSON* mockRoot = (cJSON*)0x1000; + cJSON* mockSearchResultArr = (cJSON*)0x2000; + cJSON* mockReportTypeObj = (cJSON*)0x3000; + + EXPECT_CALL(*m_reportgenMock, cJSON_Parse(_)) + .WillOnce(::testing::Return(mockRoot)); + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .WillOnce(::testing::Return(mockReportTypeObj)); + EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject(mockReportTypeObj, ::testing::StrEq("REPORT_TYPE"), ::testing::StrEq("CACHED"))) + .WillOnce(::testing::Return(mockReportTypeObj)); + EXPECT_CALL(*m_reportgenMock, cJSON_GetObjectItemCaseSensitive(mockRoot, ::testing::StrEq("searchResult"))) + .WillOnce(::testing::Return(mockSearchResultArr)); + EXPECT_CALL(*m_reportgenMock, cJSON_IsArray(mockSearchResultArr)) + .WillOnce(::testing::Return(1)); + EXPECT_CALL(*m_reportgenMock, cJSON_InsertItemInArray(mockSearchResultArr, 1, mockReportTypeObj)) + .Times(1); + char* updatedJson = strdup("{\"searchResult\":[{}, {\"REPORT_TYPE\": \"CACHED\"}]}"); + EXPECT_CALL(*m_reportgenMock, cJSON_PrintUnformatted(mockRoot)) + .WillOnce(::testing::Return(updatedJson)); + EXPECT_CALL(*m_reportgenMock, cJSON_Delete(mockRoot)) + .Times(1); + + tagReportAsCached(&fakeJson); + + EXPECT_STREQ("{\"searchResult\":[{}, {\"REPORT_TYPE\": \"CACHED\"}]}", fakeJson); + free(fakeJson); +} +TEST_F(reportgenTestFixture, tagReportAsCached_ReportAndSearchResultBranches) { + + char* fakeJson = strdup("{\"searchResult\":null, \"Report\":[]}"); + + cJSON* mockRoot = (cJSON*)0x1000; + cJSON* mockReportArray = (cJSON*)0x2000; + cJSON* mockReportTypeObj = (cJSON*)0x3000; + + // 1. Parse JSON to get root object + EXPECT_CALL(*m_reportgenMock, cJSON_Parse(_)) + .WillOnce(::testing::Return(mockRoot)); + // 2. Create REPORT_TYPE object + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .WillOnce(::testing::Return(mockReportTypeObj)); + // 3. Add string key/value to object + EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject( + mockReportTypeObj, + ::testing::StrEq("REPORT_TYPE"), + ::testing::StrEq("CACHED")) + ) + .WillOnce(::testing::Return(mockReportTypeObj)); + // 4a. First branch: request "searchResult" (simulate not found) + EXPECT_CALL(*m_reportgenMock, cJSON_GetObjectItemCaseSensitive(mockRoot, ::testing::StrEq("searchResult"))) + .WillOnce(::testing::Return(nullptr)); + // 4b. Second branch: request "Report" (returns array) + EXPECT_CALL(*m_reportgenMock, cJSON_GetObjectItemCaseSensitive(mockRoot, ::testing::StrEq("Report"))) + .WillOnce(::testing::Return(mockReportArray)); + // 5. Confirm it's an array + EXPECT_CALL(*m_reportgenMock, cJSON_IsArray(mockReportArray)) + .WillOnce(::testing::Return(1)); + // 6. Insert into array at beginning + EXPECT_CALL(*m_reportgenMock, cJSON_InsertItemInArray(mockReportArray, 0, mockReportTypeObj)) + .Times(1); + // 7. Print result after insertion + char* updatedJson = strdup("{\"searchResult\":null, \"Report\":[{\"REPORT_TYPE\": \"CACHED\"}]}"); + EXPECT_CALL(*m_reportgenMock, cJSON_PrintUnformatted(mockRoot)) + .WillOnce(::testing::Return(updatedJson)); + // 8. Cleanup + EXPECT_CALL(*m_reportgenMock, cJSON_Delete(mockRoot)) + .Times(1); + + // Act + tagReportAsCached(&fakeJson); + + // Assert + EXPECT_STREQ("{\"searchResult\":null, \"Report\":[{\"REPORT_TYPE\": \"CACHED\"}]}", fakeJson); + free(fakeJson); +} +TEST_F(reportgenTestFixture, tagReportAsCached_NoArrayErrorBranch) { + char* fakeJson = strdup("{\"foo\":42}"); + cJSON* mockRoot = (cJSON*)0x1000; + cJSON* mockReportTypeObj = (cJSON*)0x3000; + + // Parse + EXPECT_CALL(*m_reportgenMock, cJSON_Parse(_)) + .WillOnce(::testing::Return(mockRoot)); + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()) + .WillOnce(::testing::Return(mockReportTypeObj)); + EXPECT_CALL(*m_reportgenMock, cJSON_AddStringToObject( + mockReportTypeObj, + ::testing::StrEq("REPORT_TYPE"), + ::testing::StrEq("CACHED")) + ) + .WillOnce(::testing::Return(mockReportTypeObj)); + // Try "searchResult" (not found) + EXPECT_CALL(*m_reportgenMock, cJSON_GetObjectItemCaseSensitive(mockRoot, ::testing::StrEq("searchResult"))) + .WillOnce(::testing::Return(nullptr)); + // Try "Report" (not found) + EXPECT_CALL(*m_reportgenMock, cJSON_GetObjectItemCaseSensitive(mockRoot, ::testing::StrEq("Report"))) + .WillOnce(::testing::Return(nullptr)); + // Act + tagReportAsCached(&fakeJson); + + // Assert: unchanged input + EXPECT_STREQ("{\"foo\":42}", fakeJson); + free(fakeJson); +} +TEST_F(reportgenTestFixture, tagReportAsCached_CreateObjectFails) +{ + char* fakeJson = strdup("{\"searchResult\":[]}"); + cJSON* mockRoot = (cJSON*)0x1000; + + EXPECT_CALL(*m_reportgenMock, cJSON_Parse(_)).WillOnce(::testing::Return(mockRoot)); + EXPECT_CALL(*m_reportgenMock, cJSON_CreateObject()).WillOnce(::testing::Return(nullptr)); + EXPECT_CALL(*m_reportgenMock, cJSON_Delete(mockRoot)); + tagReportAsCached(&fakeJson); + + EXPECT_STREQ("{\"searchResult\":[]}", fakeJson); // Should be unchanged + + free(fakeJson); // Clean up +} +#ifdef GTEST_ENABLE +extern "C" { +typedef bool (*checkForEmptyStringFunc)(char *); +checkForEmptyStringFunc checkForEmptyStringCallback(void); + +typedef T2ERROR (*applyRegexToValueFunc)(char **,const char *); +applyRegexToValueFunc applyRegexToValueCallback(void); + +} + +TEST(applyRegexValueTest, ApplyRegexToValue_viaCallback_InputValueIsNull) { + applyRegexToValueFunc fp = applyRegexToValueCallback(); + ASSERT_NE(fp, nullptr); + EXPECT_EQ(T2ERROR_INVALID_ARGS, fp(nullptr, ".*")); +} + +TEST(applyRegexValueTest, ApplyRegexToValue_viaCallback_DereferencedInputValueIsNull) { + applyRegexToValueFunc fp = applyRegexToValueCallback(); + ASSERT_NE(fp, nullptr); + char *val = nullptr; + EXPECT_EQ(T2ERROR_INVALID_ARGS, fp(&val, ".*")); +} + +TEST(applyRegexValueTest, ApplyRegexToValue_viaCallback_RegexPatternIsNull) { + applyRegexToValueFunc fp = applyRegexToValueCallback(); + ASSERT_NE(fp, nullptr); + char val[] = "test"; + char *pval = val; + EXPECT_EQ(T2ERROR_INVALID_ARGS, fp(&pval, nullptr)); +} + +TEST(applyRegexValueTest, ApplyRegexToValue_viaCallback_ValidArguments) { + applyRegexToValueFunc fp = applyRegexToValueCallback(); + ASSERT_NE(fp, nullptr); + char *inputValue = strdup("TestString"); + const char *pattern = "Test.*"; + // Don't expect INVALID_ARGS, expect success or implementation-specific code + T2ERROR result = fp(&inputValue, pattern); + EXPECT_NE(result, T2ERROR_INVALID_ARGS); + free(inputValue); +} + +TEST_F(reportgenTestFixture, ApplyRegexToValue_viaCallback_RegexecFails) { + applyRegexToValueFunc fp = applyRegexToValueCallback(); + ASSERT_NE(fp, nullptr); + + char *input = strdup("foobar"); + ASSERT_NE(input, nullptr); + char **inputValue = &input; + const char *pattern = "^baz$"; // will not match "foobar" + + EXPECT_CALL(*m_reportgenMock, regcomp(_, StrEq(pattern), _)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*m_reportgenMock, regexec(_, StrEq("foobar"), _, _, _)) + .Times(1) + .WillOnce(Return(REG_NOMATCH)); + EXPECT_CALL(*m_reportgenMock, regfree(_)) + .Times(1); + + T2ERROR result = fp(inputValue, pattern); + + ASSERT_NE(*inputValue, nullptr); + EXPECT_STREQ(*inputValue, ""); + + // Clean up + free(*inputValue); +} + +TEST(CheckForEmptyString, AllBranchesAreCovered) +{ + checkForEmptyStringFunc cb = checkForEmptyStringCallback(); + + EXPECT_TRUE(cb(NULL)); + + char empty[] = ""; + EXPECT_TRUE(cb(empty)); + + char space[] = " "; + EXPECT_TRUE(cb(space)); + + char nulllit[] = "NULL"; + EXPECT_TRUE(cb(nulllit)); + + char nonempty[] = "VALUE"; + EXPECT_FALSE(cb(nonempty)); + + char almostnull[] = "NUL"; + EXPECT_FALSE(cb(almostnull)); +} +#endif diff --git a/source/test/scheduler/schedulerTest.cpp b/source/test/scheduler/schedulerTest.cpp index 244f73e7..3f44961a 100644 --- a/source/test/scheduler/schedulerTest.cpp +++ b/source/test/scheduler/schedulerTest.cpp @@ -157,6 +157,26 @@ TEST(GETLAPSEDTIME, T2_GT_T1) EXPECT_EQ(1, getLapsedTime(&output, &time1, &time2)); } +TEST(GETLAPSEDTIME, T1_TV_NSEC_GT_T2_BY_GT_1SEC) +{ + struct timespec time1; + struct timespec time2; + struct timespec output; + + // Make time1->tv_nsec - time2->tv_nsec > 1,000,000,000 + time1.tv_sec = 10; + time1.tv_nsec = 200000000; // 2.1 seconds in nanoseconds + time2.tv_sec = 10; + time2.tv_nsec = 100000000; // 0.001 seconds in nanoseconds + + // This will hit the "if (com)" block for the second condition + getLapsedTime(&output, &time1, &time2); + + // You can check output values if you want, but just calling is enough for coverage + EXPECT_EQ(output.tv_sec, 0); // 10-10 + EXPECT_EQ(output.tv_nsec, 100000000); +} + TEST(REGISTERSCHEWITHPROFILE_BEFORE_INITSCHEDULER, TEST1) { EXPECT_EQ(T2ERROR_INVALID_ARGS, registerProfileWithScheduler(NULL, 50, 3600, true, true, true, 10, "2022-12-20T11:05:56Z")); @@ -222,11 +242,79 @@ TEST(REGISTERSCHEWITHPROFILE_AFTER_INITSCHEDULER, REGISTER_PROFILE_AGAIN) EXPECT_EQ(T2ERROR_SUCCESS, registerProfileWithScheduler("RDKB_Profile", 10, 100, true, true, true, 15, "0001-01-01T00:00:00Z")); } +TEST(REGISTERSCHEWITHPROFILE, DUPLICATE_PROFILE) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + registerProfileWithScheduler("DUPLICATE_PROFILE", 5, 100, true, true, true, 5, "0001-01-01T00:00:00Z"); + EXPECT_EQ(T2ERROR_SUCCESS, registerProfileWithScheduler("DUPLICATE_PROFILE", 5, 100, true, true, true, 5, "0001-01-01T00:00:00Z")); + unregisterProfileFromScheduler("DUPLICATE_PROFILE"); + uninitScheduler(); +} + +TEST(UNREGISTERPROFILEFROMSCHEDULER, NULL_NAME) +{ + EXPECT_EQ(T2ERROR_INVALID_ARGS, unregisterProfileFromScheduler(NULL)); +} + + +TEST(UNREGISTERPROFILEFROMSCHEDULER, BEFORE_INIT) +{ + uninitScheduler(); // makes sure scheduler is uninitialized + EXPECT_EQ(T2ERROR_SUCCESS, unregisterProfileFromScheduler("SOME_PROFILE")); +} + + +TEST(SENDINTERRUPTTOTIMEOUTTHREAD, NOT_INITIALIZED) +{ + uninitScheduler(); // ensure not initialized + EXPECT_EQ(T2ERROR_FAILURE, SendInterruptToTimeoutThread("SHOULD_FAIL")); +} + + +TEST(UNREGISTERPROFILEFROMSCHEDULER, PROFILE_NOT_FOUND) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + EXPECT_EQ(T2ERROR_FAILURE, unregisterProfileFromScheduler("NOT_EXIST_PROFILE")); + uninitScheduler(); +} + + +TEST(REGISTERPROFILEWITHSCHEDULER, NULL_PROFILE) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + EXPECT_EQ(T2ERROR_INVALID_ARGS, registerProfileWithScheduler(NULL, 10, 100, true, true, true, 5, "2022-12-20T11:05:56Z")); + uninitScheduler(); +} + + +TEST(REGISTERPROFILEWITHSCHEDULER, NOT_INITIALIZED) +{ + uninitScheduler(); + EXPECT_EQ(T2ERROR_FAILURE, registerProfileWithScheduler("FAIL_PROFILE", 10, 100, true, true, true, 5, "2022-12-20T11:05:56Z")); +} + + +TEST(UNINITSCHEDULER, NOT_INITIALIZED) +{ + uninitScheduler(); // Should just return/log +} + + +TEST(REGISTERPROFILEWITHSCHEDULER, FIRSTREPORT_LT_TIMEOUT) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + + + registerProfileWithScheduler("REPORT_TEST", 2, 50, true, true, true, 5, "0001-01-01T00:00:00Z"); + unregisterProfileFromScheduler("REPORT_TEST"); + uninitScheduler(); +} + TEST(TIMEOUTTHREAD, TEST1) { SchedulerProfile *tProfile = (SchedulerProfile *)malloc(sizeof(SchedulerProfile)); tProfile->name = strdup("RDKB_Profile"); - tProfile->timeRefinSec = 0; + tProfile->timeRefinSec = 10; tProfile->repeat = true; tProfile->terminated = false; tProfile->timeOutDuration = 10; @@ -237,18 +325,61 @@ TEST(TIMEOUTTHREAD, TEST1) tProfile->firstexecution = true; TimeoutThread((void *)tProfile); + free(tProfile->name); + free(tProfile); +} + +TEST(TIMEOUTTHREAD, TEST2) +{ + SchedulerProfile *tProfile = (SchedulerProfile *)malloc(sizeof(SchedulerProfile)); + tProfile->name = strdup("RDKB_Profile"); + tProfile->timeRefinSec = 10; + tProfile->repeat = true; + tProfile->terminated = false; + tProfile->timeOutDuration = 20; + tProfile->timeToLive = 100; + tProfile->deleteonTime = true; + tProfile->reportonupdate = true; + tProfile->firstreportint = 20; + tProfile->firstexecution = false; + + TimeoutThread((void *)tProfile); + free(tProfile->name); + free(tProfile); +} + +TEST(TIMEOUTTHREAD, WAIT_NO_REPORTING_INTERVAL) +{ + SchedulerProfile *tProfile = (SchedulerProfile *)malloc(sizeof(SchedulerProfile)); + tProfile->name = strdup("RDKB_Profile"); + tProfile->timeRefinSec = 0; + tProfile->timeRef = NULL; + tProfile->timeOutDuration = UINT_MAX; + tProfile->repeat = false; + tProfile->terminated = false; + tProfile->timeToLive = 100; + tProfile->deleteonTime = true; + tProfile->reportonupdate = true; + tProfile->firstreportint = 0; + tProfile->firstexecution = false; + + // Call TimeoutThread directly – will exercise pthread_cond_wait branch + TimeoutThread((void *)tProfile); + + free(tProfile->name); + free(tProfile); } TEST(SendInterruptToTimeoutThread, NULL_CHECK) { EXPECT_EQ(T2ERROR_INVALID_ARGS, SendInterruptToTimeoutThread(NULL)); } - +#if 0 TEST(UNREGISTERPROFILEFROMSCH_AFTER_INITSCHEDULER, UNREGISTER_PROFILE_NOT_REGISTERED) { EXPECT_EQ(T2ERROR_FAILURE, unregisterProfileFromScheduler("Profile1")); } - +#endif TEST(UNREGISTERPROFILEFROMSCH_AFTER_INITSCHEDULER, UNREGISTER_KNOWN_PROFILE) { EXPECT_EQ(T2ERROR_SUCCESS, unregisterProfileFromScheduler("RDKB_Profile")); @@ -265,3 +396,56 @@ TEST(UNINITSCHEDULER, TEST1) registerProfileWithScheduler("RDKB_Profile", 10, 100, true, true, true, 10, "0001-01-01T00:00:00Z"); uninitScheduler(); } + +TEST(FREE_SCHEDULER_PROFILE, NULL_ARG) +{ + freeSchedulerProfile(NULL); // just returns, for coverage +} + +static void* DummySchedulerThread(void* arg) +{ + (void)arg; + return NULL; +} + +TEST(FREE_SCHEDULER_PROFILE, NORMAL) +{ + SchedulerProfile *sch = (SchedulerProfile*)calloc(1, sizeof(SchedulerProfile)); + sch->name = strdup("FREE_PROFILE"); + pthread_mutex_init(&sch->tMutex, NULL); + pthread_cond_init(&sch->tCond, NULL); + pthread_t threadId; + ASSERT_EQ(0, pthread_create(&threadId, NULL, DummySchedulerThread, NULL)); + freeSchedulerProfile(sch); +} + +TEST(UNINITSCHEDULER, CALLED_TWICE) +{ + uninitScheduler(); + uninitScheduler(); +} + +TEST(UNREGISTERPROFILEFROMSCHEDULER, ALREADY_REMOVED) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + registerProfileWithScheduler("REMOVEME", 2, 10, true, true, true, 1, "0001-01-01T00:00:00Z"); + unregisterProfileFromScheduler("REMOVEME"); + EXPECT_EQ(T2ERROR_FAILURE, unregisterProfileFromScheduler("REMOVEME")); + uninitScheduler(); +} +#ifdef GTEST_ENABLE +extern "C" +{ + typedef unsigned int (*getSchdInSec_fn)(char*); + getSchdInSec_fn getSchdInSec_fnCallback(void); +} +TEST(getSchdInSec, IndirectCall) +{ + getSchdInSec_fn fn = getSchdInSec_fnCallback(); + char timeRef[] = "2022-12-20T11:05:56Z"; + // Call via function pointer + unsigned int result = fn(timeRef); + EXPECT_GE(result, 0u); + EXPECT_LE(result, 86400u); +} +#endif diff --git a/source/test/t2parser/t2parserTest.cpp b/source/test/t2parser/t2parserTest.cpp index fb26d4a2..1ddce3dc 100644 --- a/source/test/t2parser/t2parserTest.cpp +++ b/source/test/t2parser/t2parserTest.cpp @@ -79,7 +79,7 @@ TEST(PROCESSXCONFCONFIGURATION, TEST_NULL_INVALID) ProfileXConf *profile = 0; fstream new_file; char* data = NULL; - new_file.open("xconfInputfile.txt", ios::in); + new_file.open("source/test/t2parser/xconfInputfile.txt", ios::in); if (new_file.is_open()) { string sa; @@ -143,7 +143,7 @@ TEST(PROCESSCONFIGURATION_CJSON, TEST_NULL_INVALID_PARAM) fstream new_file; string sa; int len = 0; - new_file.open("rpInputfile.txt", ios::in); + new_file.open("source/test/t2parser/rpInputfile.txt", ios::in); char* data = NULL; if (new_file.is_open()) { getline(new_file, sa); diff --git a/test/run_ut.sh b/test/run_ut.sh index e48497f3..7c5b942a 100755 --- a/test/run_ut.sh +++ b/test/run_ut.sh @@ -74,7 +74,7 @@ done if [ "$ENABLE_COV" = true ]; then echo "Generating coverage report" lcov --directory . --capture --output-file coverage.info - lcov --remove coverage.info "${PWD}/source/test/*" --output-file coverage.info + lcov --remove coverage.info "${PWD}/source/test/*" "${PWD}/source/xconf-client/*" --output-file coverage.info lcov --remove coverage.info "$HOME/usr/*" --output-file coverage.info lcov --remove coverage.info "/usr/*" --output-file coverage.info