From 23d99aab2a706c96d54c38221c1fa25f710eea79 Mon Sep 17 00:00:00 2001 From: Adrian Muzyka Date: Mon, 9 Dec 2024 13:40:30 +0100 Subject: [PATCH] RDK-54097: Add L2 Thunder R4.4.1 tests for Analytics plugin --- .github/workflows/L2-tests-R4-4-1.yml | 5 +- Analytics/CMakeLists.txt | 14 +- .../AnalyticsImplementation.cpp | 28 +- .../Backend/Sift/SiftBackend.cpp | 17 +- .../Backend/Sift/SiftConfig.cpp | 11 +- .../Backend/Sift/SiftUploader.cpp | 36 +- .../Backend/Sift/SiftUploader.h | 2 +- .../Implementation/SystemTime/SystemTime.cpp | 20 +- Tests/L2Tests/L2TestsPlugin/CMakeLists.txt | 5 + .../L2TestsPlugin/tests/Analytics_L2Test.cpp | 1083 +++++++++++++++++ Tests/mocks/Wraps.cpp | 11 + Tests/mocks/Wraps.h | 3 + Tests/mocks/WrapsMock.h | 1 + l2tests.cmake | 1 + 14 files changed, 1193 insertions(+), 44 deletions(-) create mode 100644 Tests/L2Tests/L2TestsPlugin/tests/Analytics_L2Test.cpp diff --git a/.github/workflows/L2-tests-R4-4-1.yml b/.github/workflows/L2-tests-R4-4-1.yml index 50cbaede6c..97aaa3832f 100755 --- a/.github/workflows/L2-tests-R4-4-1.yml +++ b/.github/workflows/L2-tests-R4-4-1.yml @@ -281,7 +281,10 @@ jobs: -DDS_FOUND=ON -DPLUGIN_ANALYTICS=ON -DPLUGIN_ANALYTICS_SIFT_BACKEND=ON - -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + -DPLUGIN_ANALYTICS_SIFT_2_0_ENABLED="true" + -DPLUGIN_ANALYTICS_SIFT_MAX_RANDOMISATION_WINDOW_TIME=30 + -DPLUGIN_ANALYTICS_SIFT_STORE_PATH="/tmp/AnalyticsSiftStore" + -DPLUGIN_ANALYTICS_SIFT_URL="127.0.0.1:12345" && cmake --build build/rdkservices -j8 && diff --git a/Analytics/CMakeLists.txt b/Analytics/CMakeLists.txt index 05b1ba12de..ea75e44f7e 100644 --- a/Analytics/CMakeLists.txt +++ b/Analytics/CMakeLists.txt @@ -30,7 +30,6 @@ set(MODULE_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) option(PLUGIN_ANALYTICS_SIFT_BACKEND "Enable Sift backend" OFF) - set(PLUGIN_ANALYTICS_STARTUPORDER "" CACHE STRING "To configure startup order of Analytics plugin") set(PLUGIN_ANALYTICS_AUTOSTART "false" CACHE STRING "Automatically start Analytics plugin") set(PLUGIN_ANALYTICS_DEVICE_OS_NAME "rdk" CACHE STRING "Device OS name") @@ -87,6 +86,19 @@ target_link_libraries(${MODULE_NAME} ${MODULE_NAME}Backends ${MODULE_NAME}SystemTime) +if (RDK_SERVICE_L2_TEST) + target_compile_definitions(${MODULE_NAME} PRIVATE MODULE_NAME=Plugin_${PLUGIN_NAME}) + target_compile_options(${MODULE_NAME} PRIVATE -Wno-error) + + find_library(TESTMOCKLIB_LIBRARIES NAMES TestMocklib) + if (TESTMOCKLIB_LIBRARIES) + message ("linking mock libraries ${TESTMOCKLIB_LIBRARIES} library") + target_link_libraries(${MODULE_NAME} PRIVATE ${TESTMOCKLIB_LIBRARIES}) + else (TESTMOCKLIB_LIBRARIES) + message ("Require ${TESTMOCKLIB_LIBRARIES} library") + endif (TESTMOCKLIB_LIBRARIES) +endif (RDK_SERVICES_L2_TEST) + install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) diff --git a/Analytics/Implementation/AnalyticsImplementation.cpp b/Analytics/Implementation/AnalyticsImplementation.cpp index 6b36c65f02..a6975c0c3a 100644 --- a/Analytics/Implementation/AnalyticsImplementation.cpp +++ b/Analytics/Implementation/AnalyticsImplementation.cpp @@ -162,13 +162,18 @@ namespace Plugin { queueTimeout = std::chrono::milliseconds(POPULATE_DEVICE_INFO_RETRY_MS); } - if (queueTimeout == std::chrono::milliseconds::max()) + if (mActionQueue.empty()) { - mQueueCondition.wait(lock, [this] { return !mActionQueue.empty(); }); - } - else - { - mQueueCondition.wait_for(lock, queueTimeout, [this] { return !mActionQueue.empty(); }); + if (queueTimeout == std::chrono::milliseconds::max()) + { + mQueueCondition.wait(lock, [this] + { return !mActionQueue.empty(); }); + } + else + { + mQueueCondition.wait_for(lock, queueTimeout, [this] + { return !mActionQueue.empty(); }); + } } Action action = {ACTION_TYPE_UNDEF, nullptr}; @@ -249,9 +254,18 @@ namespace Plugin { bool AnalyticsImplementation::IsSysTimeValid() { bool ret = false; + // Time is valid if system time is available and time zone is set if (mSysTime != nullptr) { - ret = mSysTime->IsSystemTimeAvailable(); + if (mSysTime->IsSystemTimeAvailable()) + { + int32_t offset = 0; + SystemTime::TimeZoneAccuracy acc = mSysTime->GetTimeZoneOffset(offset); + if (acc == SystemTime::TimeZoneAccuracy::FINAL) + { + ret = true; + } + } } return ret; diff --git a/Analytics/Implementation/Backend/Sift/SiftBackend.cpp b/Analytics/Implementation/Backend/Sift/SiftBackend.cpp index b4511c94db..62919be54c 100644 --- a/Analytics/Implementation/Backend/Sift/SiftBackend.cpp +++ b/Analytics/Implementation/Backend/Sift/SiftBackend.cpp @@ -96,13 +96,18 @@ namespace WPEFramework queueTimeout = std::chrono::milliseconds(POPULATE_CONFIG_TIMEOUT_MS); } - if (queueTimeout == std::chrono::milliseconds::max()) - { - mQueueCondition.wait(lock, [this] { return !mActionQueue.empty(); }); - } - else + if (mActionQueue.empty()) { - mQueueCondition.wait_for(lock, queueTimeout, [this] { return !mActionQueue.empty(); }); + if (queueTimeout == std::chrono::milliseconds::max()) + { + mQueueCondition.wait(lock, [this] + { return !mActionQueue.empty(); }); + } + else + { + mQueueCondition.wait_for(lock, queueTimeout, [this] + { return !mActionQueue.empty(); }); + } } Action action = {ACTION_TYPE_UNDEF, nullptr}; diff --git a/Analytics/Implementation/Backend/Sift/SiftConfig.cpp b/Analytics/Implementation/Backend/Sift/SiftConfig.cpp index 42f5fbceae..dfed3eb8bf 100644 --- a/Analytics/Implementation/Backend/Sift/SiftConfig.cpp +++ b/Analytics/Implementation/Backend/Sift/SiftConfig.cpp @@ -30,7 +30,6 @@ #define PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE "accountProfile" #define JSONRPC_THUNDER_TIMEOUT 20000 #define THUNDER_ACCESS_DEFAULT_VALUE "127.0.0.1:9998" -#define SIFT_PARTNER_ID_DFL "rdk" namespace WPEFramework { @@ -510,6 +509,7 @@ namespace WPEFramework mAttributes.proposition = config.Sift.PlatformDfl.Value(); mStoreConfig.path = config.Sift.StorePath.Value(); + SYSLOG(Logging::Startup, ("Sift Store Path: %s", mStoreConfig.path.c_str())); mStoreConfig.eventsLimit = config.Sift.EventsLimit.Value(); mUploaderConfig.url = config.Sift.Url.Value(); @@ -621,7 +621,6 @@ namespace WPEFramework { LOGERR("Failed to get AuthService link"); mMutex.lock(); - mAttributes.partnerId = SIFT_PARTNER_ID_DFL; mAttributes.activated = false; mMutex.unlock(); } @@ -667,6 +666,14 @@ namespace WPEFramework LOGINFO("Got deviceModel %s", mAttributes.deviceModel.c_str()); } + mMutex.lock(); + if (result == Core::ERROR_NONE && mAttributes.partnerId.empty() && response.HasLabel("friendly_id")) + { + mAttributes.partnerId = response["friendly_id"].String(); + LOGINFO("Got partnerId %s", mAttributes.partnerId.c_str()); + } + mMutex.unlock(); + // Get deviceFriendlyName from System.1.getFriendlyName[friendlyName] result = systemLink->Invoke(JSONRPC_THUNDER_TIMEOUT, "getFriendlyName", params, response); if (result == Core::ERROR_NONE && response.HasLabel("friendlyName")) diff --git a/Analytics/Implementation/Backend/Sift/SiftUploader.cpp b/Analytics/Implementation/Backend/Sift/SiftUploader.cpp index 814551730e..7f540da8cd 100644 --- a/Analytics/Implementation/Backend/Sift/SiftUploader.cpp +++ b/Analytics/Implementation/Backend/Sift/SiftUploader.cpp @@ -83,13 +83,12 @@ namespace WPEFramework case SiftUploader::UploaderState::RANDOMISATION_WINDOW_WAIT_STATE: { std::unique_lock lock( mMutex ); - mCondition.wait_for(lock, std::chrono::seconds(RandomisationWindowTimeGenerator()), - [this] () { return mStop; } ); if (mStop) { - LOGINFO("SiftUploader Run exit"); - return; + break; } + mCondition.wait_for(lock, std::chrono::seconds(RandomisationWindowTimeGenerator()), + [this] () { return mStop; } ); mUploaderState = SiftUploader::UploaderState::COLLECT_ANALYTICS; } @@ -135,7 +134,7 @@ namespace WPEFramework std::string resp; - uint32_t respcode; + long respcode; LOGINFO("Posting analytics events: %s", jsonEventPayload.c_str()); @@ -159,18 +158,18 @@ namespace WPEFramework { LOGERR("No collected events to be deleted"); } - - mUploaderState = UploaderState::RANDOMISATION_WINDOW_WAIT_STATE; } else { - LOGERR("Failed to post analytics event - respcode: %u, response: %s", respcode, resp.c_str()); + LOGERR("Failed to post analytics event - respcode: %ld, response: %s", respcode, resp.c_str()); } if (!resp.empty()) { validateResponse(resp, collectedEvents); } + + mUploaderState = UploaderState::RANDOMISATION_WINDOW_WAIT_STATE; } break; @@ -180,6 +179,13 @@ namespace WPEFramework } break; } + + std::unique_lock lock( mMutex ); + if (mStop) + { + LOGINFO("SiftUploader Run exit"); + return; + } } } @@ -187,7 +193,7 @@ namespace WPEFramework { bool retry = false; - if (mCurrentRetryCount == mMaxRetries) + if (mCurrentRetryCount >= mMaxRetries) { mCurrentRetryCount = 0; } @@ -195,7 +201,7 @@ namespace WPEFramework { static auto retryTime = mMinRetryPeriod; - if (retryTime > mMaxRetryPeriod) + if (retryTime >= mMaxRetryPeriod) { retryTime = mMinRetryPeriod; } @@ -203,13 +209,13 @@ namespace WPEFramework LOGINFO("Failed posting retry wait time: %d seconds, with retries completed: %d", retryTime, mCurrentRetryCount); std::unique_lock lock( mMutex ); - mCondition.wait_for(lock, std::chrono::seconds(retryTime), - [this] () { return mStop; } ); if (mStop) { // return immediately if stop is set return false; } + mCondition.wait_for(lock, std::chrono::seconds(retryTime), + [this] () { return mStop; } ); if (retryTime < mMaxRetryPeriod) { @@ -218,7 +224,7 @@ namespace WPEFramework ++mCurrentRetryCount; - retry = true; + retry = !mStop; } return retry; @@ -374,11 +380,11 @@ namespace WPEFramework return size * nmemb; } - uint32_t SiftUploader::PostJson(const std::string &url, const std::string &json, std::string &response) + long SiftUploader::PostJson(const std::string &url, const std::string &json, std::string &response) { CURL *curl; CURLcode res; - uint32_t retHttpCode = 0; + long retHttpCode = 0; if (url.empty() || json.empty()) { diff --git a/Analytics/Implementation/Backend/Sift/SiftUploader.h b/Analytics/Implementation/Backend/Sift/SiftUploader.h index a3856394c4..9c702f03ed 100644 --- a/Analytics/Implementation/Backend/Sift/SiftUploader.h +++ b/Analytics/Implementation/Backend/Sift/SiftUploader.h @@ -68,7 +68,7 @@ namespace WPEFramework void updateEventDeviceInfoIfRequired(JsonObject &event) const; void validateResponse(const std::string &response, const std::vector &events) const; - static uint32_t PostJson(const std::string& url, const std::string& json, std::string &response); + static long PostJson(const std::string& url, const std::string& json, std::string &response); SiftStorePtr mStorePtr; std::string mUrl; diff --git a/Analytics/Implementation/SystemTime/SystemTime.cpp b/Analytics/Implementation/SystemTime/SystemTime.cpp index 06d6e64b60..55a04a5319 100644 --- a/Analytics/Implementation/SystemTime/SystemTime.cpp +++ b/Analytics/Implementation/SystemTime/SystemTime.cpp @@ -101,19 +101,11 @@ namespace WPEFramework SystemTime::TimeZoneAccuracy SystemTime::GetTimeZoneOffset(int32_t &offsetSec) { - std::string tz; - std::string accuracyString; - bool isTimeAvailable = false; - { - std::lock_guard guard(mLock); - tz = mTimeZone; - accuracyString = mTimeZoneAccuracyString; - isTimeAvailable = mIsSystemTimeAvailable; - } + std::lock_guard guard(mLock); - if (isTimeAvailable) + if (mIsSystemTimeAvailable) { - std::pair tzParsed = ParseTimeZone(tz, accuracyString); + std::pair tzParsed = ParseTimeZone(mTimeZone, mTimeZoneAccuracyString); offsetSec = tzParsed.second; return tzParsed.first; } @@ -221,6 +213,12 @@ namespace WPEFramework mIsSystemTimeAvailable = false; } } + else + { + LOGERR("getTimeStatus not available, assuming time is OK"); + std::lock_guard guard(mLock); + mIsSystemTimeAvailable = true; + } } void SystemTime::UpdateTimeZone() diff --git a/Tests/L2Tests/L2TestsPlugin/CMakeLists.txt b/Tests/L2Tests/L2TestsPlugin/CMakeLists.txt index c73df2362c..de18a0679f 100755 --- a/Tests/L2Tests/L2TestsPlugin/CMakeLists.txt +++ b/Tests/L2Tests/L2TestsPlugin/CMakeLists.txt @@ -50,6 +50,11 @@ endif() if(PLUGIN_SYSTEMMODE) set(SRC_FILES ${SRC_FILES} tests/SystemMode_L2Test.cpp) endif() + +if (PLUGIN_ANALYTICS) + set(SRC_FILES ${SRC_FILES} tests/Analytics_L2Test.cpp) +endif() + add_library(${MODULE_NAME} SHARED ${SRC_FILES}) set_target_properties(${MODULE_NAME} PROPERTIES diff --git a/Tests/L2Tests/L2TestsPlugin/tests/Analytics_L2Test.cpp b/Tests/L2Tests/L2TestsPlugin/tests/Analytics_L2Test.cpp new file mode 100644 index 0000000000..d226c49013 --- /dev/null +++ b/Tests/L2Tests/L2TestsPlugin/tests/Analytics_L2Test.cpp @@ -0,0 +1,1083 @@ +#include "L2Tests.h" +#include "L2TestsMock.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_LOG(x, ...) \ + fprintf(stderr, "\033[1;32m[%s:%d](%s)" x "\n\033[0m", __FILE__, __LINE__, __FUNCTION__, getpid(), gettid(), ##__VA_ARGS__); \ + fflush(stderr); + +#define JSON_TIMEOUT (1000) +#define ANALYTICS_CALLSIGN _T("org.rdk.Analytics") +#define ANALYTICSL2TEST_CALLSIGN _T("L2tests.1") + +#define SIFT_SERVER_IP "127.0.0.1" +#define SIFT_SERVER_PORT 12345 +#define SIFT_SERVER_TIMEOUT_SEC (2*30) + +#define ZDUMP_MOCK_OUT "America/New_York Sun Mar 9 07:00:00 2008 UT = Sun Mar 9 03:00:00 2008 EDT isdst=1 gmtoff=-14400\n" \ + "America/New_York Sun Nov 2 05:59:59 2008 UT = Sun Nov 2 01:59:59 2008 EDT isdst=1 gmtoff=-14400\n" \ + "America/New_York Sun Nov 2 06:00:00 2008 UT = Sun Nov 2 01:00:00 2008 EST isdst=0 gmtoff=-18000\n" +#define TIME_MOCK_OUT 1212537600 // 2008-06-03 12:00:00 + +using ::testing::NiceMock; +using namespace WPEFramework; +using testing::StrictMock; + +char gPipeBuffer[4 * 1024]; + +class SiftServerMock { +public: + SiftServerMock() + : mSocket(-1) + { + memset(&mAddress, 0, sizeof(mAddress)); + } + + ~SiftServerMock() + { + if (mSocket != -1) { + close(mSocket); + } + } + + bool Start() + { + mSocket = socket(AF_INET, SOCK_STREAM, 0); + if (mSocket == -1) { + TEST_LOG("Socket error"); + return false; + } + + int optval = 1; + if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) { + TEST_LOG("Failed to set SO_REUSEADDR"); + close(mSocket); + return false; + } + + mAddress.sin_family = AF_INET; + mAddress.sin_addr.s_addr = inet_addr(SIFT_SERVER_IP); + mAddress.sin_port = htons(SIFT_SERVER_PORT); + + if (bind(mSocket, (struct sockaddr*)&mAddress, sizeof(mAddress)) == -1) { + TEST_LOG("Bind error"); + close(mSocket); + return false; + } + + if (listen(mSocket, 1) == -1) { + TEST_LOG("Listen error"); + close(mSocket); + return false; + } + + return true; + } + + string AwaitData(uint32_t timeoutSec, string errCodeResp = "200 OK") + { + fd_set readfds; + struct timeval timeout; + timeout.tv_sec = timeoutSec; + timeout.tv_usec = 0; + string ret; + + FD_ZERO(&readfds); + FD_SET(mSocket, &readfds); + + int result = select(mSocket + 1, &readfds, NULL, NULL, &timeout); + if (result == -1) { + TEST_LOG("Select error"); + return ret; + } else if (result == 0) { + TEST_LOG("Timeout occurred! No data received.\n"); + return ret; + } + + int client_socket; + struct sockaddr_in client_address; + socklen_t client_address_len = sizeof(client_address); + + client_socket = accept(mSocket, (struct sockaddr*)&client_address, &client_address_len); + if (client_socket == -1) { + TEST_LOG("Accept error"); + return ret; + } + struct timeval client_timeout; + client_timeout.tv_sec = timeoutSec; + client_timeout.tv_usec = 0; + + setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&client_timeout, sizeof(client_timeout)); + char rxBuf[32 * 1024]; + ssize_t bytes_received = recv(client_socket, rxBuf, sizeof(rxBuf) - 1, 0); + if (bytes_received == -1) { + TEST_LOG("Recv error"); + } else { + rxBuf[bytes_received] = '\0'; + TEST_LOG("Received data: %s\n", rxBuf); + + // Send HTTP code response + std::string httpResponse = "HTTP/1.1 " + errCodeResp + "\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 0\r\n" + "\r\n"; + + send(client_socket, httpResponse.c_str(), httpResponse.length(), 0); + // Remove header from received data + char* start = strstr(rxBuf, "\r\n\r\n"); + if (start != NULL) { + ret = string(start + 4); + } + } + + close(client_socket); + + return ret; + } + +private: + int mSocket; + struct sockaddr_in mAddress; +}; + +class AnalyticsTest : public L2TestMocks { +protected: + virtual ~AnalyticsTest() override; + +public: + AnalyticsTest(); +}; + +AnalyticsTest::AnalyticsTest() + : L2TestMocks() +{ + Core::JSONRPC::Message message; + string response; + uint32_t status = Core::ERROR_GENERAL; + + // Activate plugin in constructor + status = ActivateService("org.rdk.PersistentStore"); + EXPECT_EQ(Core::ERROR_NONE, status); + status = ActivateService("org.rdk.System"); + EXPECT_EQ(Core::ERROR_NONE, status); + + JsonObject paramsJson; + JsonObject resultJson; + + // Set PersistentStore values required by Sift + paramsJson["namespace"] = "Analytics"; + paramsJson["key"] = "deviceType"; + paramsJson["value"] = "STB"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "modelNumber"; + paramsJson["value"] = "L2Test_STB"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "manufacturer"; + paramsJson["value"] = "L2Test_Manufacturer"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "serialNumber"; + paramsJson["value"] = "L2Test_SerialNumber"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "entertainmentOSVersion"; + paramsJson["value"] = "L2Test_EntertainmentOSVersion"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "deviceAppName"; + paramsJson["value"] = "L2Test_AppName"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "deviceAppVersion"; + paramsJson["value"] = "L2Test_AppVersion"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson.Clear(); + paramsJson["namespace"] = "accountProfile"; + paramsJson["key"] = "proposition"; + paramsJson["value"] = "L2Test_Proposition"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "retailer"; + paramsJson["value"] = "L2Test_Retailer"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "jvagent"; + paramsJson["value"] = "L2Test_JVAgent"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "coam"; + paramsJson["value"] = "true"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "accountType"; + paramsJson["value"] = "L2Test_AccountType"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "operator"; + paramsJson["value"] = "L2Test_Operator"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "detailType"; + paramsJson["value"] = "L2Test_DetailType"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // Set SystemService fake values + paramsJson.Clear(); + paramsJson["territory"] = "USA"; + paramsJson["region"] = "US-USA"; + status = InvokeServiceMethod("org.rdk.System", "setTerritory", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // Mock System.getDeviceInfo by creating custom /lib/rdk/getDeviceDetails.sh + std::ofstream file("/lib/rdk/getDeviceDetails.sh"); + file << "echo 'estb_mac=01:02:03:04:05:06'\n"; + file << "echo 'model_number=RDK'\n"; + file << "echo 'friendly_id=rdkglobal'\n"; + file.close(); +} + +AnalyticsTest::~AnalyticsTest() +{ + uint32_t status = Core::ERROR_GENERAL; + + ON_CALL(*p_rBusApiImplMock, rbus_close(::testing::_)) + .WillByDefault( + ::testing::Return(RBUS_ERROR_SUCCESS)); + + status = DeactivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + status = DeactivateService("org.rdk.System"); + EXPECT_EQ(Core::ERROR_NONE, status); + + status = DeactivateService("org.rdk.PersistentStore"); + EXPECT_EQ(Core::ERROR_NONE, status); + + sleep(5); + int file_status = remove("/tmp/secure/persistent/rdkservicestore"); + // Check if the file has been successfully removed + if (file_status != 0) { + TEST_LOG("Error deleting file[/tmp/secure/persistent/rdkservicestore]"); + } else { + TEST_LOG("File[/tmp/secure/persistent/rdkservicestore] successfully deleted"); + } + + file_status = remove("/tmp/AnalyticsSiftStore.db"); + // Check if the file has been successfully removed + if (file_status != 0) { + TEST_LOG("Error deleting file[/tmp/AnalyticsSiftStore.db]"); + } else { + TEST_LOG("File[/tmp/AnalyticsSiftStore.db] successfully deleted"); + } + + file_status = remove("/opt/persistent/timeZoneDST"); + // Check if the file has been successfully removed + if (file_status != 0) { + TEST_LOG("Error deleting file[/opt/persistent/timeZoneDST]"); + } else { + TEST_LOG("File[/opt/persistent/timeZoneDST] successfully deleted"); + } +} + +TEST_F(AnalyticsTest, SendAndReceiveSignleEventQueued) +{ + ON_CALL(*p_wrapsImplMock, v_secure_popen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + [&](const char* direction, const char* command, va_list args) -> FILE* { + const char* valueToReturn = NULL; + va_list args2; + va_copy(args2, args); + char strFmt[256]; + vsnprintf(strFmt, sizeof(strFmt), command, args2); + va_end(args2); + if (strcmp(strFmt, "zdump -v America/New_York") == 0) { + valueToReturn = ZDUMP_MOCK_OUT; + } + if (valueToReturn != NULL) { + memset(gPipeBuffer, 0, sizeof(gPipeBuffer)); + strcpy(gPipeBuffer, valueToReturn); + FILE* pipe = fmemopen(gPipeBuffer, strlen(gPipeBuffer), "r"); + return pipe; + } else { + return nullptr; + } + })); + + ON_CALL(*p_wrapsImplMock, time(::testing::_)) + .WillByDefault(::testing::Invoke( + [&](time_t* arg) -> time_t { + return TIME_MOCK_OUT; + })); + + // Activate Analytics plugin + uint32_t status = ActivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + JsonObject paramsJson; + JsonObject resultJson; + paramsJson["eventName"] = "L2TestEvent"; + paramsJson["eventVersion"] = "1"; + paramsJson["eventSource"] = "L2Test"; + paramsJson["eventSourceVersion"] = "1.0.0"; + JsonObject eventPayload; + eventPayload["data"] = "random data"; + paramsJson["eventPayload"] = eventPayload; + + SiftServerMock siftServer; + EXPECT_TRUE(siftServer.Start()); + + status = InvokeServiceMethod("org.rdk.Analytics", "sendEvent", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // TimeZone not set, check if data will not come + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_EQ(eventMsg, ""); + + // Set TimeZone to FINAL what allows event to be decorated and sent to Sift server + paramsJson.Clear(); + paramsJson["timeZone"] = "America/New_York"; + paramsJson["accuracy"] = "FINAL"; + status = InvokeServiceMethod("org.rdk.System", "setTimeZoneDST", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_NE(eventMsg, ""); + + // Check if the event message contains the expected fields + JsonArray eventArray; + eventArray.FromString(eventMsg); + EXPECT_EQ(eventArray.Length(), 1); + JsonObject eventObj = eventArray[0].Object(); + + EXPECT_TRUE(eventObj.HasLabel("common_schema")); + EXPECT_TRUE(eventObj.HasLabel("env")); + EXPECT_TRUE(eventObj.HasLabel("product_name")); + EXPECT_TRUE(eventObj.HasLabel("product_version")); + EXPECT_TRUE(eventObj.HasLabel("event_schema")); + EXPECT_TRUE(eventObj.HasLabel("event_name")); + EXPECT_TRUE(eventObj.HasLabel("timestamp")); + EXPECT_TRUE(eventObj.HasLabel("event_id")); + EXPECT_TRUE(eventObj.HasLabel("event_source")); + EXPECT_TRUE(eventObj.HasLabel("event_source_version")); + EXPECT_TRUE(eventObj.HasLabel("logger_name")); + EXPECT_TRUE(eventObj.HasLabel("logger_version")); + EXPECT_TRUE(eventObj.HasLabel("partner_id")); + EXPECT_TRUE(eventObj.HasLabel("device_model")); + EXPECT_TRUE(eventObj.HasLabel("device_type")); + EXPECT_TRUE(eventObj.HasLabel("device_timezone")); + EXPECT_TRUE(eventObj.HasLabel("device_os_name")); + EXPECT_TRUE(eventObj.HasLabel("device_os_version")); + EXPECT_TRUE(eventObj.HasLabel("platform")); + EXPECT_TRUE(eventObj.HasLabel("device_manufacturer")); + EXPECT_TRUE(eventObj.HasLabel("authenticated")); + EXPECT_TRUE(eventObj.HasLabel("session_id")); + EXPECT_TRUE(eventObj.HasLabel("proposition")); + EXPECT_TRUE(eventObj.HasLabel("retailer")); + EXPECT_TRUE(eventObj.HasLabel("jv_agent")); + EXPECT_TRUE(eventObj.HasLabel("coam")); + EXPECT_TRUE(eventObj.HasLabel("device_serial_number")); + EXPECT_TRUE(eventObj.HasLabel("device_mac_address")); + EXPECT_TRUE(eventObj.HasLabel("country")); + EXPECT_TRUE(eventObj.HasLabel("region")); + EXPECT_TRUE(eventObj.HasLabel("account_type")); + EXPECT_TRUE(eventObj.HasLabel("operator")); + EXPECT_TRUE(eventObj.HasLabel("account_detail_type")); + EXPECT_TRUE(eventObj.HasLabel("event_payload")); + + EXPECT_EQ(eventObj["common_schema"].String(), "entos/common/v1"); + EXPECT_EQ(eventObj["env"].String(), "prod"); + EXPECT_EQ(eventObj["product_name"].String(), "entos"); + EXPECT_EQ(eventObj["product_version"].String(), "L2Test_EntertainmentOSVersion"); + EXPECT_EQ(eventObj["event_schema"].String(), "entos/L2TestEvent/1"); + EXPECT_EQ(eventObj["event_name"].String(), "L2TestEvent"); + EXPECT_EQ(eventObj["event_source"].String(), "L2Test"); + EXPECT_EQ(eventObj["event_source_version"].String(), "1.0.0"); + EXPECT_EQ(eventObj["logger_name"].String(), "Analytics"); + EXPECT_EQ(eventObj["partner_id"].String(), "rdkglobal"); + EXPECT_EQ(eventObj["device_model"].String(), "RDK"); + EXPECT_EQ(eventObj["device_type"].String(), "STB"); + EXPECT_EQ(eventObj["device_timezone"].Number(), -14400000); + EXPECT_EQ(eventObj["device_os_name"].String(), "rdk"); + EXPECT_EQ(eventObj["device_os_version"].String(), "L2Test_STB"); + EXPECT_EQ(eventObj["platform"].String(), "L2Test_Proposition"); + EXPECT_EQ(eventObj["device_manufacturer"].String(), "L2Test_Manufacturer"); + EXPECT_EQ(eventObj["proposition"].String(), "L2Test_Proposition"); + EXPECT_EQ(eventObj["retailer"].String(), "L2Test_Retailer"); + EXPECT_EQ(eventObj["jv_agent"].String(), "L2Test_JVAgent"); + EXPECT_EQ(eventObj["coam"].Boolean(), true); + EXPECT_EQ(eventObj["device_serial_number"].String(), "L2Test_SerialNumber"); + EXPECT_EQ(eventObj["device_mac_address"].String(), "01:02:03:04:05:06"); + EXPECT_EQ(eventObj["country"].String(), "USA"); + EXPECT_EQ(eventObj["region"].String(), "US-USA"); + EXPECT_EQ(eventObj["account_type"].String(), "L2Test_AccountType"); + EXPECT_EQ(eventObj["operator"].String(), "L2Test_Operator"); + EXPECT_EQ(eventObj["account_detail_type"].String(), "L2Test_DetailType"); + + JsonObject eventPayloadObj = eventObj["event_payload"].Object(); + EXPECT_TRUE(eventPayloadObj.HasLabel("data")); + EXPECT_EQ(eventPayloadObj["data"].String(), "random data"); +} + +TEST_F(AnalyticsTest, SendAndReceiveMultipleEventsQueued) +{ + ON_CALL(*p_wrapsImplMock, v_secure_popen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + [&](const char* direction, const char* command, va_list args) -> FILE* { + const char* valueToReturn = NULL; + va_list args2; + va_copy(args2, args); + char strFmt[256]; + vsnprintf(strFmt, sizeof(strFmt), command, args2); + va_end(args2); + if (strcmp(strFmt, "zdump -v America/New_York") == 0) { + valueToReturn = ZDUMP_MOCK_OUT; + } + if (valueToReturn != NULL) { + memset(gPipeBuffer, 0, sizeof(gPipeBuffer)); + strcpy(gPipeBuffer, valueToReturn); + FILE* pipe = fmemopen(gPipeBuffer, strlen(gPipeBuffer), "r"); + return pipe; + } else { + return nullptr; + } + })); + + ON_CALL(*p_wrapsImplMock, time(::testing::_)) + .WillByDefault(::testing::Invoke( + [&](time_t* arg) -> time_t { + return TIME_MOCK_OUT; + })); + + // Activate Analytics plugin + uint32_t status = ActivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + JsonObject paramsJson; + JsonObject resultJson; + paramsJson["eventName"] = "L2TestEvent"; + paramsJson["eventVersion"] = "1"; + paramsJson["eventSource"] = "L2Test"; + paramsJson["eventSourceVersion"] = "1.0.0"; + JsonObject eventPayload; + eventPayload["data"] = "random data"; + paramsJson["eventPayload"] = eventPayload; + + SiftServerMock siftServer; + EXPECT_TRUE(siftServer.Start()); + + // Sift Uploader should send up to 10 events per POST by default + const int eventsRcvMaxDfl = 10; + const int eventsSentNbr = 2 * eventsRcvMaxDfl; + + for (int i = 0; i < eventsSentNbr; ++i) { + status = InvokeServiceMethod("org.rdk.Analytics", "sendEvent", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + } + + // TimeZone not set, check if data will not come + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_EQ(eventMsg, ""); + + // Set TimeZone to FINAL what allows event to be decorated and sent to Sift server + paramsJson.Clear(); + paramsJson["timeZone"] = "America/New_York"; + paramsJson["accuracy"] = "FINAL"; + status = InvokeServiceMethod("org.rdk.System", "setTimeZoneDST", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + for (int i = 0; i < eventsSentNbr; i += eventsRcvMaxDfl) { + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_NE(eventMsg, ""); + + // Check if the event message contains the expected fields + JsonArray eventArray; + eventArray.FromString(eventMsg); + EXPECT_EQ(eventArray.Length(), eventsRcvMaxDfl); + + for (int n = 0; n < eventsRcvMaxDfl; ++n) { + JsonObject eventObj = eventArray[n].Object(); + + EXPECT_TRUE(eventObj.HasLabel("common_schema")); + EXPECT_TRUE(eventObj.HasLabel("env")); + EXPECT_TRUE(eventObj.HasLabel("product_name")); + EXPECT_TRUE(eventObj.HasLabel("product_version")); + EXPECT_TRUE(eventObj.HasLabel("event_schema")); + EXPECT_TRUE(eventObj.HasLabel("event_name")); + EXPECT_TRUE(eventObj.HasLabel("timestamp")); + EXPECT_TRUE(eventObj.HasLabel("event_id")); + EXPECT_TRUE(eventObj.HasLabel("event_source")); + EXPECT_TRUE(eventObj.HasLabel("event_source_version")); + EXPECT_TRUE(eventObj.HasLabel("logger_name")); + EXPECT_TRUE(eventObj.HasLabel("logger_version")); + EXPECT_TRUE(eventObj.HasLabel("partner_id")); + EXPECT_TRUE(eventObj.HasLabel("device_model")); + EXPECT_TRUE(eventObj.HasLabel("device_type")); + EXPECT_TRUE(eventObj.HasLabel("device_timezone")); + EXPECT_TRUE(eventObj.HasLabel("device_os_name")); + EXPECT_TRUE(eventObj.HasLabel("device_os_version")); + EXPECT_TRUE(eventObj.HasLabel("platform")); + EXPECT_TRUE(eventObj.HasLabel("device_manufacturer")); + EXPECT_TRUE(eventObj.HasLabel("authenticated")); + EXPECT_TRUE(eventObj.HasLabel("session_id")); + EXPECT_TRUE(eventObj.HasLabel("proposition")); + EXPECT_TRUE(eventObj.HasLabel("retailer")); + EXPECT_TRUE(eventObj.HasLabel("jv_agent")); + EXPECT_TRUE(eventObj.HasLabel("coam")); + EXPECT_TRUE(eventObj.HasLabel("device_serial_number")); + EXPECT_TRUE(eventObj.HasLabel("device_mac_address")); + EXPECT_TRUE(eventObj.HasLabel("country")); + EXPECT_TRUE(eventObj.HasLabel("region")); + EXPECT_TRUE(eventObj.HasLabel("account_type")); + EXPECT_TRUE(eventObj.HasLabel("operator")); + EXPECT_TRUE(eventObj.HasLabel("account_detail_type")); + EXPECT_TRUE(eventObj.HasLabel("event_payload")); + + EXPECT_EQ(eventObj["common_schema"].String(), "entos/common/v1"); + EXPECT_EQ(eventObj["env"].String(), "prod"); + EXPECT_EQ(eventObj["product_name"].String(), "entos"); + EXPECT_EQ(eventObj["product_version"].String(), "L2Test_EntertainmentOSVersion"); + EXPECT_EQ(eventObj["event_schema"].String(), "entos/L2TestEvent/1"); + EXPECT_EQ(eventObj["event_name"].String(), "L2TestEvent"); + EXPECT_EQ(eventObj["event_source"].String(), "L2Test"); + EXPECT_EQ(eventObj["event_source_version"].String(), "1.0.0"); + EXPECT_EQ(eventObj["logger_name"].String(), "Analytics"); + EXPECT_EQ(eventObj["partner_id"].String(), "rdkglobal"); + EXPECT_EQ(eventObj["device_model"].String(), "RDK"); + EXPECT_EQ(eventObj["device_type"].String(), "STB"); + EXPECT_EQ(eventObj["device_timezone"].Number(), -14400000); + EXPECT_EQ(eventObj["device_os_name"].String(), "rdk"); + EXPECT_EQ(eventObj["device_os_version"].String(), "L2Test_STB"); + EXPECT_EQ(eventObj["platform"].String(), "L2Test_Proposition"); + EXPECT_EQ(eventObj["device_manufacturer"].String(), "L2Test_Manufacturer"); + EXPECT_EQ(eventObj["proposition"].String(), "L2Test_Proposition"); + EXPECT_EQ(eventObj["retailer"].String(), "L2Test_Retailer"); + EXPECT_EQ(eventObj["jv_agent"].String(), "L2Test_JVAgent"); + EXPECT_EQ(eventObj["coam"].Boolean(), true); + EXPECT_EQ(eventObj["device_serial_number"].String(), "L2Test_SerialNumber"); + EXPECT_EQ(eventObj["device_mac_address"].String(), "01:02:03:04:05:06"); + EXPECT_EQ(eventObj["country"].String(), "USA"); + EXPECT_EQ(eventObj["region"].String(), "US-USA"); + EXPECT_EQ(eventObj["account_type"].String(), "L2Test_AccountType"); + EXPECT_EQ(eventObj["operator"].String(), "L2Test_Operator"); + EXPECT_EQ(eventObj["account_detail_type"].String(), "L2Test_DetailType"); + + JsonObject eventPayloadObj = eventObj["event_payload"].Object(); + EXPECT_TRUE(eventPayloadObj.HasLabel("data")); + EXPECT_EQ(eventPayloadObj["data"].String(), "random data"); + } + } +} + +TEST_F(AnalyticsTest, SendAndReceiveMultipleEventsTimeOk) +{ + ON_CALL(*p_wrapsImplMock, v_secure_popen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + [&](const char* direction, const char* command, va_list args) -> FILE* { + const char* valueToReturn = NULL; + va_list args2; + va_copy(args2, args); + char strFmt[256]; + vsnprintf(strFmt, sizeof(strFmt), command, args2); + va_end(args2); + if (strcmp(strFmt, "zdump -v America/New_York") == 0) { + valueToReturn = ZDUMP_MOCK_OUT; + } + if (valueToReturn != NULL) { + memset(gPipeBuffer, 0, sizeof(gPipeBuffer)); + strcpy(gPipeBuffer, valueToReturn); + FILE* pipe = fmemopen(gPipeBuffer, strlen(gPipeBuffer), "r"); + return pipe; + } else { + return nullptr; + } + })); + + ON_CALL(*p_wrapsImplMock, time(::testing::_)) + .WillByDefault(::testing::Invoke( + [&](time_t* arg) -> time_t { + return TIME_MOCK_OUT; + })); + + // Set TimeZone to FINAL what allows event to be decorated and sent to Sift server + + JsonObject paramsJson; + JsonObject resultJson; + paramsJson["timeZone"] = "America/New_York"; + paramsJson["accuracy"] = "FINAL"; + uint32_t status = InvokeServiceMethod("org.rdk.System", "setTimeZoneDST", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // Activate Analytics plugin + status = ActivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + paramsJson.Clear(); + paramsJson["eventName"] = "L2TestEvent"; + paramsJson["eventVersion"] = "1"; + paramsJson["eventSource"] = "L2Test"; + paramsJson["eventSourceVersion"] = "1.0.0"; + JsonObject eventPayload; + eventPayload["data"] = "random data"; + paramsJson["eventPayload"] = eventPayload; + + SiftServerMock siftServer; + EXPECT_TRUE(siftServer.Start()); + + const int eventsSentNbr = 6; + + for (int i = 0; i < eventsSentNbr; ++i) { + status = InvokeServiceMethod("org.rdk.Analytics", "sendEvent", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + } + + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_NE(eventMsg, ""); + + // Check if the event message contains the expected fields + JsonArray eventArray; + eventArray.FromString(eventMsg); + EXPECT_EQ(eventArray.Length(), eventsSentNbr); + + for (int n = 0; n < eventsSentNbr; ++n) { + JsonObject eventObj = eventArray[n].Object(); + + EXPECT_TRUE(eventObj.HasLabel("common_schema")); + EXPECT_TRUE(eventObj.HasLabel("env")); + EXPECT_TRUE(eventObj.HasLabel("product_name")); + EXPECT_TRUE(eventObj.HasLabel("product_version")); + EXPECT_TRUE(eventObj.HasLabel("event_schema")); + EXPECT_TRUE(eventObj.HasLabel("event_name")); + EXPECT_TRUE(eventObj.HasLabel("timestamp")); + EXPECT_TRUE(eventObj.HasLabel("event_id")); + EXPECT_TRUE(eventObj.HasLabel("event_source")); + EXPECT_TRUE(eventObj.HasLabel("event_source_version")); + EXPECT_TRUE(eventObj.HasLabel("logger_name")); + EXPECT_TRUE(eventObj.HasLabel("logger_version")); + EXPECT_TRUE(eventObj.HasLabel("partner_id")); + EXPECT_TRUE(eventObj.HasLabel("device_model")); + EXPECT_TRUE(eventObj.HasLabel("device_type")); + EXPECT_TRUE(eventObj.HasLabel("device_timezone")); + EXPECT_TRUE(eventObj.HasLabel("device_os_name")); + EXPECT_TRUE(eventObj.HasLabel("device_os_version")); + EXPECT_TRUE(eventObj.HasLabel("platform")); + EXPECT_TRUE(eventObj.HasLabel("device_manufacturer")); + EXPECT_TRUE(eventObj.HasLabel("authenticated")); + EXPECT_TRUE(eventObj.HasLabel("session_id")); + EXPECT_TRUE(eventObj.HasLabel("proposition")); + EXPECT_TRUE(eventObj.HasLabel("retailer")); + EXPECT_TRUE(eventObj.HasLabel("jv_agent")); + EXPECT_TRUE(eventObj.HasLabel("coam")); + EXPECT_TRUE(eventObj.HasLabel("device_serial_number")); + EXPECT_TRUE(eventObj.HasLabel("device_mac_address")); + EXPECT_TRUE(eventObj.HasLabel("country")); + EXPECT_TRUE(eventObj.HasLabel("region")); + EXPECT_TRUE(eventObj.HasLabel("account_type")); + EXPECT_TRUE(eventObj.HasLabel("operator")); + EXPECT_TRUE(eventObj.HasLabel("account_detail_type")); + EXPECT_TRUE(eventObj.HasLabel("event_payload")); + + EXPECT_EQ(eventObj["common_schema"].String(), "entos/common/v1"); + EXPECT_EQ(eventObj["env"].String(), "prod"); + EXPECT_EQ(eventObj["product_name"].String(), "entos"); + EXPECT_EQ(eventObj["product_version"].String(), "L2Test_EntertainmentOSVersion"); + EXPECT_EQ(eventObj["event_schema"].String(), "entos/L2TestEvent/1"); + EXPECT_EQ(eventObj["event_name"].String(), "L2TestEvent"); + EXPECT_EQ(eventObj["event_source"].String(), "L2Test"); + EXPECT_EQ(eventObj["event_source_version"].String(), "1.0.0"); + EXPECT_EQ(eventObj["logger_name"].String(), "Analytics"); + EXPECT_EQ(eventObj["partner_id"].String(), "rdkglobal"); + EXPECT_EQ(eventObj["device_model"].String(), "RDK"); + EXPECT_EQ(eventObj["device_type"].String(), "STB"); + EXPECT_EQ(eventObj["device_timezone"].Number(), -14400000); + EXPECT_EQ(eventObj["device_os_name"].String(), "rdk"); + EXPECT_EQ(eventObj["device_os_version"].String(), "L2Test_STB"); + EXPECT_EQ(eventObj["platform"].String(), "L2Test_Proposition"); + EXPECT_EQ(eventObj["device_manufacturer"].String(), "L2Test_Manufacturer"); + EXPECT_EQ(eventObj["proposition"].String(), "L2Test_Proposition"); + EXPECT_EQ(eventObj["retailer"].String(), "L2Test_Retailer"); + EXPECT_EQ(eventObj["jv_agent"].String(), "L2Test_JVAgent"); + EXPECT_EQ(eventObj["coam"].Boolean(), true); + EXPECT_EQ(eventObj["device_serial_number"].String(), "L2Test_SerialNumber"); + EXPECT_EQ(eventObj["device_mac_address"].String(), "01:02:03:04:05:06"); + EXPECT_EQ(eventObj["country"].String(), "USA"); + EXPECT_EQ(eventObj["region"].String(), "US-USA"); + EXPECT_EQ(eventObj["account_type"].String(), "L2Test_AccountType"); + EXPECT_EQ(eventObj["operator"].String(), "L2Test_Operator"); + EXPECT_EQ(eventObj["account_detail_type"].String(), "L2Test_DetailType"); + + JsonObject eventPayloadObj = eventObj["event_payload"].Object(); + EXPECT_TRUE(eventPayloadObj.HasLabel("data")); + EXPECT_EQ(eventPayloadObj["data"].String(), "random data"); + } +} + +TEST_F(AnalyticsTest, OnServer400Error) +{ + ON_CALL(*p_wrapsImplMock, v_secure_popen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + [&](const char* direction, const char* command, va_list args) -> FILE* { + const char* valueToReturn = NULL; + va_list args2; + va_copy(args2, args); + char strFmt[256]; + vsnprintf(strFmt, sizeof(strFmt), command, args2); + va_end(args2); + if (strcmp(strFmt, "zdump -v America/New_York") == 0) { + valueToReturn = ZDUMP_MOCK_OUT; + } + if (valueToReturn != NULL) { + memset(gPipeBuffer, 0, sizeof(gPipeBuffer)); + strcpy(gPipeBuffer, valueToReturn); + FILE* pipe = fmemopen(gPipeBuffer, strlen(gPipeBuffer), "r"); + return pipe; + } else { + return nullptr; + } + })); + + ON_CALL(*p_wrapsImplMock, time(::testing::_)) + .WillByDefault(::testing::Invoke( + [&](time_t* arg) -> time_t { + return TIME_MOCK_OUT; + })); + + // Set TimeZone to FINAL what allows event to be decorated and sent to Sift server + JsonObject paramsJson; + JsonObject resultJson; + paramsJson["timeZone"] = "America/New_York"; + paramsJson["accuracy"] = "FINAL"; + uint32_t status = InvokeServiceMethod("org.rdk.System", "setTimeZoneDST", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // Activate Analytics plugin + status = ActivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + paramsJson.Clear(); + paramsJson["eventName"] = "L2TestEvent"; + paramsJson["eventVersion"] = "1"; + paramsJson["eventSource"] = "L2Test"; + paramsJson["eventSourceVersion"] = "1.0.0"; + JsonObject eventPayload; + eventPayload["data"] = "random data"; + paramsJson["eventPayload"] = eventPayload; + + SiftServerMock siftServer; + EXPECT_TRUE(siftServer.Start()); + + status = InvokeServiceMethod("org.rdk.Analytics", "sendEvent", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC, "400 Error"); + EXPECT_NE(eventMsg, ""); + + // On Err 400 events should be deleted + eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_EQ(eventMsg, ""); +} + +TEST_F(AnalyticsTest, OnServer500Error) +{ + ON_CALL(*p_wrapsImplMock, v_secure_popen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + [&](const char* direction, const char* command, va_list args) -> FILE* { + const char* valueToReturn = NULL; + va_list args2; + va_copy(args2, args); + char strFmt[256]; + vsnprintf(strFmt, sizeof(strFmt), command, args2); + va_end(args2); + if (strcmp(strFmt, "zdump -v America/New_York") == 0) { + valueToReturn = ZDUMP_MOCK_OUT; + } + if (valueToReturn != NULL) { + memset(gPipeBuffer, 0, sizeof(gPipeBuffer)); + strcpy(gPipeBuffer, valueToReturn); + FILE* pipe = fmemopen(gPipeBuffer, strlen(gPipeBuffer), "r"); + return pipe; + } else { + return nullptr; + } + })); + + ON_CALL(*p_wrapsImplMock, time(::testing::_)) + .WillByDefault(::testing::Invoke( + [&](time_t* arg) -> time_t { + return TIME_MOCK_OUT; + })); + + // Set TimeZone to FINAL what allows event to be decorated and sent to Sift server + JsonObject paramsJson; + JsonObject resultJson; + paramsJson["timeZone"] = "America/New_York"; + paramsJson["accuracy"] = "FINAL"; + uint32_t status = InvokeServiceMethod("org.rdk.System", "setTimeZoneDST", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // Activate Analytics plugin + status = ActivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + paramsJson.Clear(); + paramsJson["eventName"] = "L2TestEvent"; + paramsJson["eventVersion"] = "1"; + paramsJson["eventSource"] = "L2Test"; + paramsJson["eventSourceVersion"] = "1.0.0"; + JsonObject eventPayload; + eventPayload["data"] = "random data"; + paramsJson["eventPayload"] = eventPayload; + + SiftServerMock siftServer; + EXPECT_TRUE(siftServer.Start()); + + status = InvokeServiceMethod("org.rdk.Analytics", "sendEvent", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC, "500 Error"); + EXPECT_NE(eventMsg, ""); + + // On Err 500 events should be resent + string eventMsgRep = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_EQ(eventMsgRep, eventMsg); +} + +TEST_F(AnalyticsTest, OnPersistentStoreValChange) +{ + ON_CALL(*p_wrapsImplMock, v_secure_popen(::testing::_, ::testing::_, ::testing::_)) + .WillByDefault(::testing::Invoke( + [&](const char* direction, const char* command, va_list args) -> FILE* { + const char* valueToReturn = NULL; + va_list args2; + va_copy(args2, args); + char strFmt[256]; + vsnprintf(strFmt, sizeof(strFmt), command, args2); + va_end(args2); + if (strcmp(strFmt, "zdump -v America/New_York") == 0) { + valueToReturn = ZDUMP_MOCK_OUT; + } + if (valueToReturn != NULL) { + memset(gPipeBuffer, 0, sizeof(gPipeBuffer)); + strcpy(gPipeBuffer, valueToReturn); + FILE* pipe = fmemopen(gPipeBuffer, strlen(gPipeBuffer), "r"); + return pipe; + } else { + return nullptr; + } + })); + + ON_CALL(*p_wrapsImplMock, time(::testing::_)) + .WillByDefault(::testing::Invoke( + [&](time_t* arg) -> time_t { + return TIME_MOCK_OUT; + })); + + // Set TimeZone to FINAL what allows event to be decorated and sent to Sift server + JsonObject paramsJson; + JsonObject resultJson; + + paramsJson["timeZone"] = "America/New_York"; + paramsJson["accuracy"] = "FINAL"; + uint32_t status = InvokeServiceMethod("org.rdk.System", "setTimeZoneDST", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + // Activate Analytics plugin + status = ActivateService("org.rdk.Analytics"); + EXPECT_EQ(Core::ERROR_NONE, status); + + // Wait so the initial Persistent Store val are red + sleep(5); + + paramsJson.Clear(); + + // update PersistentStore values + paramsJson["namespace"] = "Analytics"; + paramsJson["key"] = "deviceType"; + paramsJson["value"] = "STB2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "modelNumber"; + paramsJson["value"] = "L2Test_STB2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "manufacturer"; + paramsJson["value"] = "L2Test_Manufacturer2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "serialNumber"; + paramsJson["value"] = "L2Test_SerialNumber2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "entertainmentOSVersion"; + paramsJson["value"] = "L2Test_EntertainmentOSVersion2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "deviceAppName"; + paramsJson["value"] = "L2Test_AppName2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "deviceAppVersion"; + paramsJson["value"] = "L2Test_AppVersion2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson.Clear(); + paramsJson["namespace"] = "accountProfile"; + paramsJson["key"] = "proposition"; + paramsJson["value"] = "L2Test_Proposition2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "retailer"; + paramsJson["value"] = "L2Test_Retailer2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "jvagent"; + paramsJson["value"] = "L2Test_JVAgent2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "coam"; + paramsJson["value"] = "false"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "accountType"; + paramsJson["value"] = "L2Test_AccountType2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "operator"; + paramsJson["value"] = "L2Test_Operator2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson["key"] = "detailType"; + paramsJson["value"] = "L2Test_DetailType2"; + status = InvokeServiceMethod("org.rdk.PersistentStore", "setValue", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + paramsJson.Clear(); + paramsJson["eventName"] = "L2TestEvent"; + paramsJson["eventVersion"] = "1"; + paramsJson["eventSource"] = "L2Test"; + paramsJson["eventSourceVersion"] = "1.0.0"; + JsonObject eventPayload; + eventPayload["data"] = "random data"; + paramsJson["eventPayload"] = eventPayload; + + SiftServerMock siftServer; + EXPECT_TRUE(siftServer.Start()); + + status = InvokeServiceMethod("org.rdk.Analytics", "sendEvent", paramsJson, resultJson); + EXPECT_EQ(status, Core::ERROR_NONE); + + string eventMsg = siftServer.AwaitData(SIFT_SERVER_TIMEOUT_SEC); + EXPECT_NE(eventMsg, ""); + + // Check if the event message contains the expected fields + JsonArray eventArray; + eventArray.FromString(eventMsg); + EXPECT_EQ(eventArray.Length(), 1); + JsonObject eventObj = eventArray[0].Object(); + + EXPECT_TRUE(eventObj.HasLabel("common_schema")); + EXPECT_TRUE(eventObj.HasLabel("env")); + EXPECT_TRUE(eventObj.HasLabel("product_name")); + EXPECT_TRUE(eventObj.HasLabel("product_version")); + EXPECT_TRUE(eventObj.HasLabel("event_schema")); + EXPECT_TRUE(eventObj.HasLabel("event_name")); + EXPECT_TRUE(eventObj.HasLabel("timestamp")); + EXPECT_TRUE(eventObj.HasLabel("event_id")); + EXPECT_TRUE(eventObj.HasLabel("event_source")); + EXPECT_TRUE(eventObj.HasLabel("event_source_version")); + EXPECT_TRUE(eventObj.HasLabel("logger_name")); + EXPECT_TRUE(eventObj.HasLabel("logger_version")); + EXPECT_TRUE(eventObj.HasLabel("partner_id")); + EXPECT_TRUE(eventObj.HasLabel("device_model")); + EXPECT_TRUE(eventObj.HasLabel("device_type")); + EXPECT_TRUE(eventObj.HasLabel("device_timezone")); + EXPECT_TRUE(eventObj.HasLabel("device_os_name")); + EXPECT_TRUE(eventObj.HasLabel("device_os_version")); + EXPECT_TRUE(eventObj.HasLabel("platform")); + EXPECT_TRUE(eventObj.HasLabel("device_manufacturer")); + EXPECT_TRUE(eventObj.HasLabel("authenticated")); + EXPECT_TRUE(eventObj.HasLabel("session_id")); + EXPECT_TRUE(eventObj.HasLabel("proposition")); + EXPECT_TRUE(eventObj.HasLabel("retailer")); + EXPECT_TRUE(eventObj.HasLabel("jv_agent")); + EXPECT_TRUE(eventObj.HasLabel("coam")); + EXPECT_TRUE(eventObj.HasLabel("device_serial_number")); + EXPECT_TRUE(eventObj.HasLabel("device_mac_address")); + EXPECT_TRUE(eventObj.HasLabel("country")); + EXPECT_TRUE(eventObj.HasLabel("region")); + EXPECT_TRUE(eventObj.HasLabel("account_type")); + EXPECT_TRUE(eventObj.HasLabel("operator")); + EXPECT_TRUE(eventObj.HasLabel("account_detail_type")); + EXPECT_TRUE(eventObj.HasLabel("event_payload")); + + EXPECT_EQ(eventObj["common_schema"].String(), "entos/common/v1"); + EXPECT_EQ(eventObj["env"].String(), "prod"); + EXPECT_EQ(eventObj["product_name"].String(), "entos"); + EXPECT_EQ(eventObj["product_version"].String(), "L2Test_EntertainmentOSVersion2"); + EXPECT_EQ(eventObj["event_schema"].String(), "entos/L2TestEvent/1"); + EXPECT_EQ(eventObj["event_name"].String(), "L2TestEvent"); + EXPECT_EQ(eventObj["event_source"].String(), "L2Test"); + EXPECT_EQ(eventObj["event_source_version"].String(), "1.0.0"); + EXPECT_EQ(eventObj["logger_name"].String(), "Analytics"); + EXPECT_EQ(eventObj["partner_id"].String(), "rdkglobal"); + EXPECT_EQ(eventObj["device_model"].String(), "RDK"); + EXPECT_EQ(eventObj["device_type"].String(), "STB2"); + EXPECT_EQ(eventObj["device_timezone"].Number(), -14400000); + EXPECT_EQ(eventObj["device_os_name"].String(), "rdk"); + EXPECT_EQ(eventObj["device_os_version"].String(), "L2Test_STB2"); + EXPECT_EQ(eventObj["platform"].String(), "L2Test_Proposition2"); + EXPECT_EQ(eventObj["device_manufacturer"].String(), "L2Test_Manufacturer2"); + EXPECT_EQ(eventObj["proposition"].String(), "L2Test_Proposition2"); + EXPECT_EQ(eventObj["retailer"].String(), "L2Test_Retailer2"); + EXPECT_EQ(eventObj["jv_agent"].String(), "L2Test_JVAgent2"); + EXPECT_EQ(eventObj["coam"].Boolean(), false); + EXPECT_EQ(eventObj["device_serial_number"].String(), "L2Test_SerialNumber2"); + EXPECT_EQ(eventObj["device_mac_address"].String(), "01:02:03:04:05:06"); + EXPECT_EQ(eventObj["country"].String(), "USA"); + EXPECT_EQ(eventObj["region"].String(), "US-USA"); + EXPECT_EQ(eventObj["account_type"].String(), "L2Test_AccountType2"); + EXPECT_EQ(eventObj["operator"].String(), "L2Test_Operator2"); + EXPECT_EQ(eventObj["account_detail_type"].String(), "L2Test_DetailType2"); + + JsonObject eventPayloadObj = eventObj["event_payload"].Object(); + EXPECT_TRUE(eventPayloadObj.HasLabel("data")); + EXPECT_EQ(eventPayloadObj["data"].String(), "random data"); +} diff --git a/Tests/mocks/Wraps.cpp b/Tests/mocks/Wraps.cpp index 8a61086adf..2ff5b4815d 100644 --- a/Tests/mocks/Wraps.cpp +++ b/Tests/mocks/Wraps.cpp @@ -71,6 +71,11 @@ extern "C" ssize_t __wrap_readlink(const char *pathname, char *buf, size_t bufsi return Wraps::getInstance().readlink(pathname, buf, bufsiz); } +extern "C" time_t __wrap_time(time_t *arg) +{ + return Wraps::getInstance().time(arg); +} + WrapsImpl* Wraps::impl = nullptr; Wraps::Wraps() {} @@ -149,3 +154,9 @@ ssize_t Wraps::readlink(const char *pathname, char *buf, size_t bufsiz) return impl->readlink(pathname,buf,bufsiz); } +time_t Wraps::time(time_t* arg) +{ + EXPECT_NE(impl, nullptr); + return impl->time(arg); +} + diff --git a/Tests/mocks/Wraps.h b/Tests/mocks/Wraps.h index 8dd7da37a7..ff34a0fdaf 100644 --- a/Tests/mocks/Wraps.h +++ b/Tests/mocks/Wraps.h @@ -18,6 +18,7 @@ class WrapsImpl { virtual int v_secure_pclose(FILE *) = 0; virtual int v_secure_system(const char *command, va_list args) =0; virtual ssize_t readlink(const char *pathname, char *buf, size_t bufsiz) = 0; + virtual time_t time(time_t* arg) = 0; }; class Wraps { @@ -49,4 +50,6 @@ class Wraps { static int v_secure_system(const char *command, va_list args); ssize_t readlink(const char *pathname, char *buf, size_t bufsiz); + + static time_t time(time_t* arg); }; diff --git a/Tests/mocks/WrapsMock.h b/Tests/mocks/WrapsMock.h index 2041eab9ee..b6f99728e1 100644 --- a/Tests/mocks/WrapsMock.h +++ b/Tests/mocks/WrapsMock.h @@ -32,4 +32,5 @@ class WrapsImplMock : public WrapsImpl { MOCK_METHOD(int, v_secure_pclose, (FILE *file), (override)); MOCK_METHOD(int, v_secure_system,(const char *command, va_list args), (override)); MOCK_METHOD(ssize_t, readlink, (const char *pathname, char *buf, size_t bufsiz), (override)); + MOCK_METHOD(time_t, time, (time_t* arg), (override)); }; \ No newline at end of file diff --git a/l2tests.cmake b/l2tests.cmake index 210d8e474a..df184033d5 100755 --- a/l2tests.cmake +++ b/l2tests.cmake @@ -118,6 +118,7 @@ if (USE_THUNDER_R4) set(PLUGIN_PERSISTENTSTORE ON) set(PLUGIN_USERSETTINGS ON) set(PLUGIN_SYSTEMMODE ON) + set(PLUGIN_ANALYTICS ON) endif (USE_THUNDER_R4) set(PLUGIN_L2Tests ON) set(BUILD_SHARED_LIBS ON)