diff --git a/unit_test.sh b/unit_test.sh old mode 100755 new mode 100644 index 19b583bd..48857c28 --- a/unit_test.sh +++ b/unit_test.sh @@ -97,6 +97,7 @@ for test in \ ./../uploadstblogs/unittest/strategies_gtest \ ./../uploadstblogs/unittest/strategy_handler_gtest \ ./../uploadstblogs/unittest/uploadlogsnow_gtest \ + ./../uploadstblogs/unittest/file_operations_gtest \ ./../usbLogUpload/unittest/usb_log_file_manager_gtest \ ./../usbLogUpload/unittest/usb_log_validation_gtest \ ./../usbLogUpload/unittest/usb_log_utils_gtest \ @@ -168,3 +169,4 @@ if [ "$ENABLE_COV" = true ]; then echo "WARNING: No coverage data found in any module; skipping combined report generation" fi fi + diff --git a/uploadstblogs/src/archive_manager.c b/uploadstblogs/src/archive_manager.c index 430d71b9..ed2b25f9 100755 --- a/uploadstblogs/src/archive_manager.c +++ b/uploadstblogs/src/archive_manager.c @@ -828,3 +828,45 @@ int create_dri_archive(RuntimeContext* ctx, SessionState* session, const char* a return create_archive_with_options(ctx, session, ctx->dri_log_path, ctx->dri_log_path, "DRI_Logs"); } + +#ifdef GTEST_ENABLE +bool (*getCopyLogFile(void))(const char*, const char*) +{ + return ©_log_file; +} + +int (*getCollectFilesFromDir(void))(const char*, const char*, bool (*)(const char*)) +{ + return &collect_files_from_dir; +} + +bool (*getGenerateArchiveNameAt(void))(char*, size_t, const char*, const char*, time_t) +{ + return &generate_archive_name_at; +} + +unsigned int (*getCalculateTarChecksum(void))(struct tar_header*) +{ + return &calculate_tar_checksum; +} + +int (*getWriteTarHeader(void))(gzFile, const char*, struct stat*) +{ + return &write_tar_header; +} + +int (*getAddFileToTar(void))(gzFile, const char*, const char*) +{ + return &add_file_to_tar; +} + +int (*getAddDirectoryToTar(void))(gzFile, const char*, const char*, const char*) +{ + return &add_directory_to_tar; +} + +int (*getCreateArchiveWithOptions(void))(RuntimeContext*, SessionState*, const char*, const char*, const char*) +{ + return &create_archive_with_options; +} +#endif diff --git a/uploadstblogs/src/cleanup_handler.c b/uploadstblogs/src/cleanup_handler.c index 25087a4d..cfc47021 100755 --- a/uploadstblogs/src/cleanup_handler.c +++ b/uploadstblogs/src/cleanup_handler.c @@ -557,3 +557,10 @@ bool create_block_marker(UploadPath path, int duration_seconds) return false; } } + +#ifdef GTEST_ENABLE +int (*getRemoveDirectoryRecursive(void))(const char*) +{ + return &remove_directory_recursive; +} +#endif diff --git a/uploadstblogs/src/strategies.c b/uploadstblogs/src/strategies.c index 93c9319d..71c9f3e8 100644 --- a/uploadstblogs/src/strategies.c +++ b/uploadstblogs/src/strategies.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include "strategy_handler.h" #include "archive_manager.h" @@ -1599,3 +1600,96 @@ static int reboot_cleanup(RuntimeContext* ctx, SessionState* session, bool uploa return 0; } + + +#ifdef GTEST_ENABLE +int (*getGetJsonRpc(void))(char *, DownloadData *) +{ + return &getJsonRpc; +} + +bool (*getReadDcmUploadFlag(void))(void) +{ + return &read_dcm_upload_flag; +} + +int (*getDcmSetup(void))(RuntimeContext*, SessionState*) +{ + return &dcm_setup; +} + +int (*getDcmArchive(void))(RuntimeContext*, SessionState*) +{ + return &dcm_archive; +} + +int (*getDcmUpload(void))(RuntimeContext*, SessionState*) +{ + return &dcm_upload; +} + +int (*getDcmCleanup(void))(RuntimeContext*, SessionState*, bool) +{ + return &dcm_cleanup; +} + +int (*getOndemandSetup(void))(RuntimeContext*, SessionState*) +{ + return &ondemand_setup; +} + +int (*getOndemandArchive(void))(RuntimeContext*, SessionState*) +{ + return &ondemand_archive; +} + +int (*getOndemandUpload(void))(RuntimeContext*, SessionState*) +{ + return &ondemand_upload; +} + +int (*getOndemandCleanup(void))(RuntimeContext*, SessionState*, bool) +{ + return &ondemand_cleanup; +} + +int (*getRebootSetup(void))(RuntimeContext*, SessionState*) +{ + return &reboot_setup; +} + +int (*getRebootArchive(void))(RuntimeContext*, SessionState*) +{ + return &reboot_archive; +} + +int (*getRebootUpload(void))(RuntimeContext*, SessionState*) +{ + return &reboot_upload; +} + +int (*getRebootCleanup(void))(RuntimeContext*, SessionState*, bool) +{ + return &reboot_cleanup; +} + +int (*getCopyOptLogsFiles(void))(const char*, const char*) +{ + return ©_opt_logs_files; +} + +int (*getCopyDirRecursive(void))(const char*, const char*) +{ + return ©_dir_recursive; +} + +int (*getCopyAllFilesToDcm(void))(const char*, const char*) +{ + return ©_all_files_to_dcm; +} + +int (*getProcessDcmUploadList(void))(RuntimeContext*) +{ + return &process_dcm_upload_list; +} +#endif diff --git a/uploadstblogs/src/upload_engine.c b/uploadstblogs/src/upload_engine.c index ebfa8b65..5be51023 100755 --- a/uploadstblogs/src/upload_engine.c +++ b/uploadstblogs/src/upload_engine.c @@ -238,3 +238,10 @@ int upload_archive(RuntimeContext* ctx, SessionState* session, const char* archi return -1; } } + +#ifdef GTEST_ENABLE +UploadResult (*getSingleAttemptUpload(void))(RuntimeContext*, SessionState*, UploadPath) +{ + return &single_attempt_upload; +} +#endif diff --git a/uploadstblogs/src/uploadlogsnow.c b/uploadstblogs/src/uploadlogsnow.c index 22b7dd69..86f233e6 100644 --- a/uploadstblogs/src/uploadlogsnow.c +++ b/uploadstblogs/src/uploadlogsnow.c @@ -332,3 +332,20 @@ int execute_uploadlogsnow_workflow(RuntimeContext* ctx) return ret; } + +#ifdef GTEST_ENABLE +int (*getWriteUploadStatus(void))(const char*) +{ + return &write_upload_status; +} + +int (*getShouldExcludeFile(void))(const char*) +{ + return &should_exclude_file; +} + +int (*getCopyFilesToDcmPath(void))(const char*, const char*) +{ + return ©_files_to_dcm_path; +} +#endif diff --git a/uploadstblogs/unittest/Makefile.am b/uploadstblogs/unittest/Makefile.am old mode 100755 new mode 100644 index eb616e13..2d6b30d6 --- a/uploadstblogs/unittest/Makefile.am +++ b/uploadstblogs/unittest/Makefile.am @@ -1,150 +1,156 @@ -########################################################################## -# If not stated otherwise in this file or this component's LICENSE -# file the following copyright and licenses apply: -# -# Copyright 2025 RDK Management -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -########################################################################## - -AUTOMAKE_OPTIONS = subdir-objects - -# Define the test executables -bin_PROGRAMS = context_manager_gtest md5_utils_gtest validation_gtest strategy_selector_gtest \ - path_handler_gtest archive_manager_gtest upload_engine_gtest \ - cleanup_handler_gtest verification_gtest \ - rbus_interface_gtest uploadstblogs_gtest event_manager_gtest \ - retry_logic_gtest strategies_gtest \ - strategy_handler_gtest uploadlogsnow_gtest - -# Common include directories -COMMON_CPPFLAGS = -std=c++11 -I. -I/usr/include/cjson -I../ -I../../ -I/usr/include -I../include -I./mocks \ - -I../src -I$(top_srcdir)/include -I$(top_srcdir)/../common_utilities/utils \ - -I$(top_srcdir)/../common_utilities/parsejson -I$(top_srcdir)/../common_utilities/dwnlutils \ - -I$(top_srcdir)/../common_utilities/uploadutil \ - -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/dbus-1.0 \ - -I${PKG_CONFIG_SYSROOT_DIR}$(libdir)/dbus-1.0/include \ - -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rbus \ - -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rdk/iarmbus \ - -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rdk/iarmmgrs/sysmgr \ - -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rdk/iarmmgrs-hal \ - -I/usr/include/gtest -I/usr/local/include -I/usr/local/include/gtest -DGTEST_ENABLE -DGTEST_BASIC -DEN_MAINTENANCE_MANAGER -DIARM_ENABLED - -AM_CPPFLAGS = -I$(top_srcdir)/unittest/mocks -I$(top_srcdir)/include -I$(top_srcdir)/mocks -I$(top_srcdir) -I/usr/include -AM_CXXFLAGS = -std=c++11 - -# Common libraries -COMMON_LDADD = -lgtest -lgmock -lpthread -lcurl -lcjson -lssl -lcrypto -lgcov -lz -lrbus -lsecure_wrapper \ - -lfwutils -lrdkloggers - -# Common compiler flags -COMMON_CXXFLAGS = -frtti -fprofile-arcs -ftest-coverage -fpermissive -Wno-write-strings -Wno-unused-result -Wno-error -Wno-format-truncation - -# Define source files for each test - -context_manager_gtest_SOURCES = context_manager_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_rbus.cpp ./mocks/mock_file_operations.cpp -context_manager_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -context_manager_gtest_LDADD = $(COMMON_LDADD) -context_manager_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -context_manager_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -md5_utils_gtest_SOURCES = md5_utils_gtest.cpp -md5_utils_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -md5_utils_gtest_LDADD = $(COMMON_LDADD) -md5_utils_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -md5_utils_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -validation_gtest_SOURCES = validation_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_file_operations.cpp -validation_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -validation_gtest_LDADD = $(COMMON_LDADD) -validation_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -validation_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -strategy_selector_gtest_SOURCES = strategy_selector_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_file_operations.cpp -strategy_selector_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -strategy_selector_gtest_LDADD = $(COMMON_LDADD) -strategy_selector_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -strategy_selector_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -path_handler_gtest_SOURCES = path_handler_gtest.cpp ./mocks/mock_curl.cpp -path_handler_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -path_handler_gtest_LDADD = $(COMMON_LDADD) -path_handler_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -path_handler_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -archive_manager_gtest_SOURCES = archive_manager_gtest.cpp ./mocks/mock_file_operations.cpp -archive_manager_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -archive_manager_gtest_LDADD = $(COMMON_LDADD) -archive_manager_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -archive_manager_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -upload_engine_gtest_SOURCES = upload_engine_gtest.cpp ./mocks/mock_curl.cpp -upload_engine_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -upload_engine_gtest_LDADD = $(COMMON_LDADD) -upload_engine_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -upload_engine_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -cleanup_handler_gtest_SOURCES = cleanup_handler_gtest.cpp ./mocks/mock_file_operations.cpp -cleanup_handler_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -cleanup_handler_gtest_LDADD = $(COMMON_LDADD) -cleanup_handler_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -cleanup_handler_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -verification_gtest_SOURCES = verification_gtest.cpp -verification_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -verification_gtest_LDADD = $(COMMON_LDADD) -verification_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -verification_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -rbus_interface_gtest_SOURCES = rbus_interface_gtest.cpp ./mocks/mock_rbus.cpp -rbus_interface_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -rbus_interface_gtest_LDADD = $(COMMON_LDADD) -rbus_interface_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -rbus_interface_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -uploadstblogs_gtest_SOURCES = uploadstblogs_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_rbus.cpp ./mocks/mock_curl.cpp -uploadstblogs_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -uploadstblogs_gtest_LDADD = $(COMMON_LDADD) -uploadstblogs_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -uploadstblogs_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -event_manager_gtest_SOURCES = event_manager_gtest.cpp -event_manager_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -event_manager_gtest_LDADD = $(COMMON_LDADD) -event_manager_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -event_manager_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -retry_logic_gtest_SOURCES = retry_logic_gtest.cpp -retry_logic_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -retry_logic_gtest_LDADD = $(COMMON_LDADD) -retry_logic_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -retry_logic_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -strategies_gtest_SOURCES = strategies_gtest.cpp -strategies_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -strategies_gtest_LDADD = $(COMMON_LDADD) -strategies_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -strategies_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -strategy_handler_gtest_SOURCES = strategy_handler_gtest.cpp -strategy_handler_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -strategy_handler_gtest_LDADD = $(COMMON_LDADD) -strategy_handler_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -strategy_handler_gtest_CFLAGS = $(COMMON_CXXFLAGS) - -uploadlogsnow_gtest_SOURCES = uploadlogsnow_gtest.cpp ../src/uploadlogsnow.c -uploadlogsnow_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) -uploadlogsnow_gtest_LDADD = $(COMMON_LDADD) -uploadlogsnow_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) -uploadlogsnow_gtest_CFLAGS = $(COMMON_CXXFLAGS) - +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################## + +AUTOMAKE_OPTIONS = subdir-objects + +# Define the test executables +bin_PROGRAMS = context_manager_gtest md5_utils_gtest validation_gtest strategy_selector_gtest \ + path_handler_gtest archive_manager_gtest upload_engine_gtest \ + cleanup_handler_gtest verification_gtest \ + rbus_interface_gtest uploadstblogs_gtest event_manager_gtest \ + retry_logic_gtest strategies_gtest \ + strategy_handler_gtest uploadlogsnow_gtest file_operations_gtest + +# Common include directories +COMMON_CPPFLAGS = -std=c++11 -I. -I/usr/include/cjson -I../ -I../../ -I/usr/include -I../include -I./mocks \ + -I../src -I$(top_srcdir)/include -I$(top_srcdir)/../common_utilities/utils \ + -I$(top_srcdir)/../common_utilities/parsejson -I$(top_srcdir)/../common_utilities/dwnlutils \ + -I$(top_srcdir)/../common_utilities/uploadutil \ + -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/dbus-1.0 \ + -I${PKG_CONFIG_SYSROOT_DIR}$(libdir)/dbus-1.0/include \ + -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rbus \ + -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rdk/iarmbus \ + -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rdk/iarmmgrs/sysmgr \ + -I${PKG_CONFIG_SYSROOT_DIR}$(includedir)/rdk/iarmmgrs-hal \ + -I/usr/include/gtest -I/usr/local/include -I/usr/local/include/gtest -DGTEST_ENABLE -DGTEST_BASIC -DEN_MAINTENANCE_MANAGER -DIARM_ENABLED + +AM_CPPFLAGS = -I$(top_srcdir)/unittest/mocks -I$(top_srcdir)/include -I$(top_srcdir)/mocks -I$(top_srcdir) -I/usr/include +AM_CXXFLAGS = -std=c++11 + +# Common libraries +COMMON_LDADD = -lgtest -lgmock -lpthread -lcurl -lcjson -lssl -lcrypto -lgcov -lz -lrbus -lsecure_wrapper \ + -lfwutils -lrdkloggers + +# Common compiler flags +COMMON_CXXFLAGS = -frtti -fprofile-arcs -ftest-coverage -fpermissive -Wno-write-strings -Wno-unused-result -Wno-error -Wno-format-truncation + +# Define source files for each test + +context_manager_gtest_SOURCES = context_manager_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_rbus.cpp ./mocks/mock_file_operations.cpp +context_manager_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +context_manager_gtest_LDADD = $(COMMON_LDADD) +context_manager_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +context_manager_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +md5_utils_gtest_SOURCES = md5_utils_gtest.cpp +md5_utils_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +md5_utils_gtest_LDADD = $(COMMON_LDADD) +md5_utils_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +md5_utils_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +validation_gtest_SOURCES = validation_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_file_operations.cpp +validation_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +validation_gtest_LDADD = $(COMMON_LDADD) +validation_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +validation_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +strategy_selector_gtest_SOURCES = strategy_selector_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_file_operations.cpp +strategy_selector_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +strategy_selector_gtest_LDADD = $(COMMON_LDADD) +strategy_selector_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +strategy_selector_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +path_handler_gtest_SOURCES = path_handler_gtest.cpp ./mocks/mock_curl.cpp +path_handler_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +path_handler_gtest_LDADD = $(COMMON_LDADD) +path_handler_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +path_handler_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +archive_manager_gtest_SOURCES = archive_manager_gtest.cpp ./mocks/mock_file_operations.cpp +archive_manager_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +archive_manager_gtest_LDADD = $(COMMON_LDADD) +archive_manager_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +archive_manager_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +upload_engine_gtest_SOURCES = upload_engine_gtest.cpp ./mocks/mock_curl.cpp +upload_engine_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +upload_engine_gtest_LDADD = $(COMMON_LDADD) +upload_engine_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +upload_engine_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +cleanup_handler_gtest_SOURCES = cleanup_handler_gtest.cpp ./mocks/mock_file_operations.cpp +cleanup_handler_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +cleanup_handler_gtest_LDADD = $(COMMON_LDADD) +cleanup_handler_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +cleanup_handler_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +verification_gtest_SOURCES = verification_gtest.cpp +verification_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +verification_gtest_LDADD = $(COMMON_LDADD) +verification_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +verification_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +rbus_interface_gtest_SOURCES = rbus_interface_gtest.cpp ./mocks/mock_rbus.cpp +rbus_interface_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +rbus_interface_gtest_LDADD = $(COMMON_LDADD) +rbus_interface_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +rbus_interface_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +uploadstblogs_gtest_SOURCES = uploadstblogs_gtest.cpp ./mocks/mock_rdk_utils.cpp ./mocks/mock_rbus.cpp ./mocks/mock_curl.cpp +uploadstblogs_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +uploadstblogs_gtest_LDADD = $(COMMON_LDADD) +uploadstblogs_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +uploadstblogs_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +event_manager_gtest_SOURCES = event_manager_gtest.cpp +event_manager_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +event_manager_gtest_LDADD = $(COMMON_LDADD) +event_manager_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +event_manager_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +retry_logic_gtest_SOURCES = retry_logic_gtest.cpp +retry_logic_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +retry_logic_gtest_LDADD = $(COMMON_LDADD) +retry_logic_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +retry_logic_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +strategies_gtest_SOURCES = strategies_gtest.cpp +strategies_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +strategies_gtest_LDADD = $(COMMON_LDADD) +strategies_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +strategies_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +strategy_handler_gtest_SOURCES = strategy_handler_gtest.cpp +strategy_handler_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +strategy_handler_gtest_LDADD = $(COMMON_LDADD) +strategy_handler_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +strategy_handler_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +uploadlogsnow_gtest_SOURCES = uploadlogsnow_gtest.cpp ../src/uploadlogsnow.c +uploadlogsnow_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +uploadlogsnow_gtest_LDADD = $(COMMON_LDADD) +uploadlogsnow_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +uploadlogsnow_gtest_CFLAGS = $(COMMON_CXXFLAGS) + +file_operations_gtest_SOURCES = file_operations_gtest.cpp +file_operations_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +file_operations_gtest_LDADD = $(COMMON_LDADD) +file_operations_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) +file_operations_gtest_CFLAGS = $(COMMON_CXXFLAGS) + diff --git a/uploadstblogs/unittest/archive_manager_gtest.cpp b/uploadstblogs/unittest/archive_manager_gtest.cpp index 172391c0..663003ed 100755 --- a/uploadstblogs/unittest/archive_manager_gtest.cpp +++ b/uploadstblogs/unittest/archive_manager_gtest.cpp @@ -595,6 +595,78 @@ TEST_F(ArchiveManagerTest, ShouldCollectFile_EdgeCases) { EXPECT_FALSE(should_collect_file("txt")); // No extension } +/* ========================== + generate_archive_name Tests + ========================== */ + +TEST_F(ArchiveManagerTest, GenerateArchiveName_NullBuffer) { + EXPECT_FALSE(generate_archive_name(nullptr, 256, "AA:BB:CC:DD:EE:FF", "Logs")); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_NullPrefix) { + char buffer[256]; + EXPECT_FALSE(generate_archive_name(buffer, sizeof(buffer), "AA:BB:CC:DD:EE:FF", nullptr)); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_BufferTooSmall) { + char buffer[32]; + EXPECT_FALSE(generate_archive_name(buffer, sizeof(buffer), "AA:BB:CC:DD:EE:FF", "Logs")); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_NullMAC) { + char buffer[256]; + EXPECT_FALSE(generate_archive_name(buffer, sizeof(buffer), nullptr, "Logs")); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_EmptyMAC) { + char buffer[256]; + EXPECT_FALSE(generate_archive_name(buffer, sizeof(buffer), "", "Logs")); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_Success_ContainsTgz) { + char buffer[256]; + bool result = generate_archive_name(buffer, sizeof(buffer), "AA:BB:CC:DD:EE:FF", "Logs"); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, ".tgz") != nullptr); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_Success_ContainsPrefix) { + char buffer[256]; + bool result = generate_archive_name(buffer, sizeof(buffer), "AA:BB:CC:DD:EE:FF", "Logs"); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "_Logs_") != nullptr); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_Success_RemovesColons) { + char buffer[256]; + bool result = generate_archive_name(buffer, sizeof(buffer), "A8:4A:63:1E:37:A5", "Logs"); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "A84A631E37A5") != nullptr); + EXPECT_TRUE(strstr(buffer, ":") == nullptr); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_DRI_Prefix) { + char buffer[256]; + bool result = generate_archive_name(buffer, sizeof(buffer), "AA:BB:CC:DD:EE:FF", "DRI_Logs"); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "_DRI_Logs_") != nullptr); + EXPECT_TRUE(strstr(buffer, ".tgz") != nullptr); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_MACWithoutColons) { + char buffer[256]; + bool result = generate_archive_name(buffer, sizeof(buffer), "AABBCCDDEEFF", "Logs"); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "AABBCCDDEEFF") != nullptr); +} + +TEST_F(ArchiveManagerTest, GenerateArchiveName_ExactMinBufferSize) { + char buffer[64]; + bool result = generate_archive_name(buffer, sizeof(buffer), "AA:BB:CC:DD:EE:FF", "Logs"); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, ".tgz") != nullptr); +} + // Test collect_logs function TEST_F(ArchiveManagerTest, CollectLogs_NullParameters) { EXPECT_EQ(collect_logs(nullptr, &session, "/tmp/dest"), -1); @@ -695,6 +767,433 @@ TEST_F(ArchiveManagerTest, CollectDriLogs_Success) { EXPECT_GE(result, 0); } +// ==================== STATIC FUNCTION ACCESSOR TESTS ==================== +// Tests for static functions exposed via #ifdef GTEST_ENABLE accessor pattern +// (same approach as strategies.c) + + + bool (*getCopyLogFile(void))(const char*, const char*); + int (*getCollectFilesFromDir(void))(const char*, const char*, bool (*)(const char*)); + bool (*getGenerateArchiveNameAt(void))(char*, size_t, const char*, const char*, time_t); + unsigned int (*getCalculateTarChecksum(void))(struct tar_header*); + int (*getWriteTarHeader(void))(gzFile, const char*, struct stat*); + int (*getAddFileToTar(void))(gzFile, const char*, const char*); + int (*getAddDirectoryToTar(void))(gzFile, const char*, const char*, const char*); + int (*getCreateArchiveWithOptions(void))(RuntimeContext*, SessionState*, const char*, const char*, const char*); + + +// ---- copy_log_file tests ---- + +class CopyLogFileTest : public ::testing::Test { +protected: + void SetUp() override { + g_mockFileOperations = new MockFileOperations(); + fnCopyLogFile = getCopyLogFile(); + ASSERT_NE(nullptr, fnCopyLogFile); + } + void TearDown() override { + delete g_mockFileOperations; + g_mockFileOperations = nullptr; + } + bool (*fnCopyLogFile)(const char*, const char*); +}; + +TEST_F(CopyLogFileTest, NullSrcPath_ReturnsFalse) { + EXPECT_FALSE(fnCopyLogFile(nullptr, "/tmp/dest")); +} + +TEST_F(CopyLogFileTest, NullDestDir_ReturnsFalse) { + EXPECT_FALSE(fnCopyLogFile("/tmp/test.log", nullptr)); +} + +TEST_F(CopyLogFileTest, Success_ExtractsFilename) { + EXPECT_CALL(*g_mockFileOperations, copy_file(_, _)) + .WillOnce(Return(true)); + + EXPECT_TRUE(fnCopyLogFile("/opt/logs/test.log", "/tmp/dest")); +} + +TEST_F(CopyLogFileTest, Success_NoSlashInPath) { + EXPECT_CALL(*g_mockFileOperations, copy_file(_, _)) + .WillOnce(Return(true)); + + EXPECT_TRUE(fnCopyLogFile("test.log", "/tmp/dest")); +} + +TEST_F(CopyLogFileTest, CopyFails_ReturnsFalse) { + EXPECT_CALL(*g_mockFileOperations, copy_file(_, _)) + .WillOnce(Return(false)); + + EXPECT_FALSE(fnCopyLogFile("/opt/logs/test.log", "/tmp/dest")); +} + +// ---- collect_files_from_dir tests ---- + +class CollectFilesFromDirTest : public ::testing::Test { +protected: + void SetUp() override { + g_mockFileOperations = new MockFileOperations(); + g_readdir_call_count = 0; + g_opendir_call_count = 0; + fnCollectFilesFromDir = getCollectFilesFromDir(); + ASSERT_NE(nullptr, fnCollectFilesFromDir); + } + void TearDown() override { + delete g_mockFileOperations; + g_mockFileOperations = nullptr; + } + int (*fnCollectFilesFromDir)(const char*, const char*, bool (*)(const char*)); +}; + +TEST_F(CollectFilesFromDirTest, NullSrcDir_ReturnsError) { + EXPECT_EQ(-1, fnCollectFilesFromDir(nullptr, "/tmp/dest", nullptr)); +} + +TEST_F(CollectFilesFromDirTest, NullDestDir_ReturnsError) { + EXPECT_EQ(-1, fnCollectFilesFromDir("/opt/logs", nullptr, nullptr)); +} + +TEST_F(CollectFilesFromDirTest, SrcDirNotExists_ReturnsZero) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillOnce(Return(false)); + + EXPECT_EQ(0, fnCollectFilesFromDir("/opt/logs", "/tmp/dest", nullptr)); +} + +TEST_F(CollectFilesFromDirTest, OpendirFails_ReturnsError) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillOnce(Return(true)); + + // opendir mock returns nullptr for paths containing "fail" + EXPECT_EQ(-1, fnCollectFilesFromDir("/fail", "/tmp/dest", nullptr)); +} + +TEST_F(CollectFilesFromDirTest, WithFilter_CollectsMatchingFiles) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillOnce(Return(true)); + EXPECT_CALL(*g_mockFileOperations, copy_file(_, _)) + .WillRepeatedly(Return(true)); + + // readdir mock returns test.log and another.log (both pass should_collect_file) + int result = fnCollectFilesFromDir("/opt/logs", "/tmp/dest", should_collect_file); + EXPECT_GE(result, 0); +} + +TEST_F(CollectFilesFromDirTest, NoFilter_CollectsAllFiles) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillOnce(Return(true)); + EXPECT_CALL(*g_mockFileOperations, copy_file(_, _)) + .WillRepeatedly(Return(true)); + + int result = fnCollectFilesFromDir("/opt/logs", "/tmp/dest", nullptr); + EXPECT_GE(result, 0); +} + +// ---- generate_archive_name_at tests ---- + +class GenerateArchiveNameAtTest : public ::testing::Test { +protected: + void SetUp() override { + fnGenerateArchiveNameAt = getGenerateArchiveNameAt(); + ASSERT_NE(nullptr, fnGenerateArchiveNameAt); + } + void TearDown() override {} + bool (*fnGenerateArchiveNameAt)(char*, size_t, const char*, const char*, time_t); +}; + +TEST_F(GenerateArchiveNameAtTest, Success_GeneratesCorrectFormat) { + char buffer[256]; + // 1642780800 = 2022-01-21 12:00:00 UTC + bool result = fnGenerateArchiveNameAt(buffer, sizeof(buffer), + "AA:BB:CC:DD:EE:FF", "Logs", + (time_t)1642780800); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "AABBCCDDEEFF") != nullptr); + EXPECT_TRUE(strstr(buffer, "_Logs_") != nullptr); + EXPECT_TRUE(strstr(buffer, ".tgz") != nullptr); + EXPECT_TRUE(strstr(buffer, ":") == nullptr); +} + +TEST_F(GenerateArchiveNameAtTest, RemovesColonsFromMAC) { + char buffer[256]; + bool result = fnGenerateArchiveNameAt(buffer, sizeof(buffer), + "A8:4A:63:1E:37:A5", "Logs", + (time_t)1642780800); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "A84A631E37A5") != nullptr); +} + +TEST_F(GenerateArchiveNameAtTest, DRI_Prefix) { + char buffer[256]; + bool result = fnGenerateArchiveNameAt(buffer, sizeof(buffer), + "AA:BB:CC:DD:EE:FF", "DRI_Logs", + (time_t)1642780800); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "_DRI_Logs_") != nullptr); +} + +TEST_F(GenerateArchiveNameAtTest, MACWithoutColons_Unchanged) { + char buffer[256]; + bool result = fnGenerateArchiveNameAt(buffer, sizeof(buffer), + "AABBCCDDEEFF", "Logs", + (time_t)1642780800); + EXPECT_TRUE(result); + EXPECT_TRUE(strstr(buffer, "AABBCCDDEEFF") != nullptr); +} + +TEST_F(GenerateArchiveNameAtTest, RefTimeUsedInTimestamp) { + char buf1[256], buf2[256]; + fnGenerateArchiveNameAt(buf1, sizeof(buf1), "AA:BB:CC:DD:EE:FF", "Logs", + (time_t)1642780800); + fnGenerateArchiveNameAt(buf2, sizeof(buf2), "AA:BB:CC:DD:EE:FF", "Logs", + (time_t)1642867200); + // Different ref_time should produce different archive names + EXPECT_STRNE(buf1, buf2); +} + +// ---- calculate_tar_checksum tests ---- + +class CalculateTarChecksumTest : public ::testing::Test { +protected: + void SetUp() override { + fnCalculateTarChecksum = getCalculateTarChecksum(); + ASSERT_NE(nullptr, fnCalculateTarChecksum); + } + void TearDown() override {} + unsigned int (*fnCalculateTarChecksum)(struct tar_header*); +}; + +TEST_F(CalculateTarChecksumTest, ZeroHeader_ReturnsSpacesSum) { + struct tar_header header; + memset(&header, 0, sizeof(header)); + + unsigned int sum = fnCalculateTarChecksum(&header); + // Checksum field (8 bytes) is filled with spaces (0x20 each) = 8 * 32 = 256 + EXPECT_EQ(sum, 256u); +} + +TEST_F(CalculateTarChecksumTest, NonZeroHeader_IncludesAllBytes) { + struct tar_header header; + memset(&header, 0, sizeof(header)); + strncpy(header.name, "test.log", sizeof(header.name) - 1); + snprintf(header.mode, sizeof(header.mode), "%07o", 0644); + + unsigned int sum = fnCalculateTarChecksum(&header); + // Sum should be greater than just spaces (256) + EXPECT_GT(sum, 256u); +} + +TEST_F(CalculateTarChecksumTest, Deterministic_SameInputSameOutput) { + struct tar_header header; + memset(&header, 0, sizeof(header)); + strncpy(header.name, "file.log", sizeof(header.name) - 1); + + unsigned int sum1 = fnCalculateTarChecksum(&header); + + // Reset checksum field (calculate_tar_checksum modifies it) + memset(&header, 0, sizeof(header)); + strncpy(header.name, "file.log", sizeof(header.name) - 1); + + unsigned int sum2 = fnCalculateTarChecksum(&header); + EXPECT_EQ(sum1, sum2); +} + +// ---- write_tar_header tests ---- + +class WriteTarHeaderTest : public ::testing::Test { +protected: + void SetUp() override { + fnWriteTarHeader = getWriteTarHeader(); + ASSERT_NE(nullptr, fnWriteTarHeader); + } + void TearDown() override {} + int (*fnWriteTarHeader)(gzFile, const char*, struct stat*); +}; + +TEST_F(WriteTarHeaderTest, Success_WritesHeader) { + struct stat st; + memset(&st, 0, sizeof(st)); + st.st_mode = S_IFREG | 0644; + st.st_size = 1024; + st.st_mtime = 1642780800; + + int result = fnWriteTarHeader(mock_gz_ptr, "test.log", &st); + EXPECT_EQ(0, result); +} + +TEST_F(WriteTarHeaderTest, GzwriteFails_ReturnsError) { + struct stat st; + memset(&st, 0, sizeof(st)); + st.st_mode = S_IFREG | 0644; + st.st_size = 1024; + + // Use invalid gzFile to trigger gzwrite failure + int result = fnWriteTarHeader(nullptr, "test.log", &st); + EXPECT_EQ(-1, result); +} + +// ---- add_file_to_tar tests ---- + +class AddFileToTarTest : public ::testing::Test { +protected: + void SetUp() override { + g_mockFileOperations = new MockFileOperations(); + g_fread_call_count = 0; + fnAddFileToTar = getAddFileToTar(); + ASSERT_NE(nullptr, fnAddFileToTar); + } + void TearDown() override { + delete g_mockFileOperations; + g_mockFileOperations = nullptr; + } + int (*fnAddFileToTar)(gzFile, const char*, const char*); +}; + +TEST_F(AddFileToTarTest, OpenFails_ReturnsError) { + // "missing" triggers stat mock to return -1 + int result = fnAddFileToTar(mock_gz_ptr, "/tmp/missing_file", "missing_file"); + EXPECT_EQ(-1, result); +} + +TEST_F(AddFileToTarTest, RegularFile_Success) { + int result = fnAddFileToTar(mock_gz_ptr, "/tmp/test.log", "test.log"); + // May succeed or fail depending on open() mock; verify no crash + EXPECT_TRUE(result == 0 || result == -1); +} + +// ---- add_directory_to_tar tests ---- + +class AddDirectoryToTarTest : public ::testing::Test { +protected: + void SetUp() override { + g_mockFileOperations = new MockFileOperations(); + g_readdir_call_count = 0; + g_opendir_call_count = 0; + fnAddDirectoryToTar = getAddDirectoryToTar(); + ASSERT_NE(nullptr, fnAddDirectoryToTar); + } + void TearDown() override { + delete g_mockFileOperations; + g_mockFileOperations = nullptr; + } + int (*fnAddDirectoryToTar)(gzFile, const char*, const char*, const char*); +}; + +TEST_F(AddDirectoryToTarTest, OpendirFails_ReturnsError) { + int result = fnAddDirectoryToTar(mock_gz_ptr, "/fail", "/fail", nullptr); + EXPECT_EQ(-1, result); +} + +TEST_F(AddDirectoryToTarTest, EmptyDir_ReturnsSuccess) { + // readdir returns . and .. then nullptr + int result = fnAddDirectoryToTar(mock_gz_ptr, "/tmp/emptydir", "/tmp/emptydir", nullptr); + // opendir mock may limit calls; just verify no crash + EXPECT_TRUE(result == 0 || result == -1); +} + +TEST_F(AddDirectoryToTarTest, ExcludesSpecifiedFile) { + int result = fnAddDirectoryToTar(mock_gz_ptr, "/tmp/logs", "/tmp/logs", + "/tmp/logs/test.log"); + // test.log should be excluded; verify no crash + EXPECT_TRUE(result == 0 || result == -1); +} + +// ---- create_archive_with_options tests ---- + +class CreateArchiveWithOptionsTest : public ::testing::Test { +protected: + void SetUp() override { + g_mockFileOperations = new MockFileOperations(); + g_readdir_call_count = 0; + g_opendir_call_count = 0; + memset(&ctx, 0, sizeof(ctx)); + memset(&session, 0, sizeof(session)); + strcpy(ctx.mac_address, "AA:BB:CC:DD:EE:FF"); + strcpy(ctx.device_type, "TEST_DEVICE"); + fnCreateArchiveWithOptions = getCreateArchiveWithOptions(); + ASSERT_NE(nullptr, fnCreateArchiveWithOptions); + } + void TearDown() override { + delete g_mockFileOperations; + g_mockFileOperations = nullptr; + } + RuntimeContext ctx; + SessionState session; + int (*fnCreateArchiveWithOptions)(RuntimeContext*, SessionState*, + const char*, const char*, const char*); +}; + +TEST_F(CreateArchiveWithOptionsTest, NullCtx_ReturnsError) { + EXPECT_EQ(-1, fnCreateArchiveWithOptions(nullptr, &session, "/tmp", nullptr, "Logs")); +} + +TEST_F(CreateArchiveWithOptionsTest, NullSession_ReturnsError) { + EXPECT_EQ(-1, fnCreateArchiveWithOptions(&ctx, nullptr, "/tmp", nullptr, "Logs")); +} + +TEST_F(CreateArchiveWithOptionsTest, NullSourceDir_ReturnsError) { + EXPECT_EQ(-1, fnCreateArchiveWithOptions(&ctx, &session, nullptr, nullptr, "Logs")); +} + +TEST_F(CreateArchiveWithOptionsTest, NullPrefix_ReturnsError) { + EXPECT_EQ(-1, fnCreateArchiveWithOptions(&ctx, &session, "/tmp", nullptr, nullptr)); +} + +TEST_F(CreateArchiveWithOptionsTest, SourceDirNotExist_ReturnsError) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillOnce(Return(false)); + + EXPECT_EQ(-1, fnCreateArchiveWithOptions(&ctx, &session, "/tmp/nope", nullptr, "Logs")); +} + +TEST_F(CreateArchiveWithOptionsTest, Success_StoresArchiveFilename) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockFileOperations, file_exists(_)) + .WillRepeatedly(Return(true)); + + int result = fnCreateArchiveWithOptions(&ctx, &session, "/tmp", nullptr, "Logs"); + if (result == 0) { + EXPECT_TRUE(strlen(session.archive_file) > 0); + EXPECT_TRUE(strstr(session.archive_file, "_Logs_") != nullptr); + EXPECT_TRUE(strstr(session.archive_file, ".tgz") != nullptr); + } +} + +TEST_F(CreateArchiveWithOptionsTest, CustomOutputDir_Used) { + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockFileOperations, file_exists(_)) + .WillRepeatedly(Return(true)); + + int result = fnCreateArchiveWithOptions(&ctx, &session, "/tmp/src", + "/tmp/out", "DRI_Logs"); + if (result == 0) { + EXPECT_TRUE(strstr(session.archive_file, "_DRI_Logs_") != nullptr); + } +} + +TEST_F(CreateArchiveWithOptionsTest, EmptyMAC_FailsGracefully) { + strcpy(ctx.mac_address, ""); + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillRepeatedly(Return(true)); + + int result = fnCreateArchiveWithOptions(&ctx, &session, "/tmp", nullptr, "Logs"); + // generate_archive_name returns false for empty MAC -> returns -1 + EXPECT_EQ(-1, result); +} + +TEST_F(CreateArchiveWithOptionsTest, ArchiveRefTimeUsed_WhenSet) { + ctx.archive_ref_time = (time_t)1642780800; + EXPECT_CALL(*g_mockFileOperations, dir_exists(_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockFileOperations, file_exists(_)) + .WillRepeatedly(Return(true)); + + int result = fnCreateArchiveWithOptions(&ctx, &session, "/tmp", nullptr, "Logs"); + EXPECT_TRUE(result == 0 || result == -1); +} + GTEST_API_ int main(int argc, char *argv[]){ char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; char buffer[GTEST_REPORT_FILEPATH_SIZE]; diff --git a/uploadstblogs/unittest/cleanup_handler_gtest.cpp b/uploadstblogs/unittest/cleanup_handler_gtest.cpp index c491b9e1..9f29075b 100755 --- a/uploadstblogs/unittest/cleanup_handler_gtest.cpp +++ b/uploadstblogs/unittest/cleanup_handler_gtest.cpp @@ -200,6 +200,53 @@ int rmdir(const char *pathname) { } return 0; } + +// Mock unlink for remove_archive and cleanup_temp_dirs +static bool unlink_fail = false; +static bool unlink_enoent = false; +static int unlink_call_count = 0; + +int unlink(const char *pathname) { + unlink_call_count++; + if (!pathname) { + errno = EINVAL; + return -1; + } + if (unlink_enoent) { + errno = ENOENT; + return -1; + } + if (unlink_fail) { + errno = EACCES; + return -1; + } + return 0; +} + +// Mock fopen/fclose/fprintf for create_block_marker +static bool fopen_fail = false; +static FILE* mock_file_ptr = (FILE*)0x5678; +static int fopen_call_count = 0; + +FILE* fopen(const char *pathname, const char *mode) { + fopen_call_count++; + if (fopen_fail || !pathname) { + return NULL; + } + return mock_file_ptr; +} + +int fclose(FILE *stream) { + return 0; +} + +int fprintf(FILE *stream, const char *format, ...) { + return 10; +} + +// Accessor for static function +int (*getRemoveDirectoryRecursive(void))(const char*); + #endif } @@ -223,6 +270,11 @@ class CleanupManagerTest : public ::testing::Test { remove_fail = false; mock_readdir_count = 0; total_opendir_calls = 0; + unlink_fail = false; + unlink_enoent = false; + unlink_call_count = 0; + fopen_fail = false; + fopen_call_count = 0; // Set up test directory structure strcpy(test_log_path, "/opt/logs"); @@ -385,6 +437,310 @@ TEST_F(CleanupManagerTest, ArchiveCleanup_FileTypes) { EXPECT_GE(result, 0); } +// ==================== remove_archive TESTS ==================== + +TEST_F(CleanupManagerTest, RemoveArchive_NullPath) { + EXPECT_FALSE(remove_archive(nullptr)); +} + +TEST_F(CleanupManagerTest, RemoveArchive_EmptyPath) { + EXPECT_FALSE(remove_archive("")); +} + +TEST_F(CleanupManagerTest, RemoveArchive_Success) { + unlink_fail = false; + EXPECT_TRUE(remove_archive("/tmp/test_archive.tgz")); + EXPECT_EQ(unlink_call_count, 1); +} + +TEST_F(CleanupManagerTest, RemoveArchive_FileNotExist_ReturnsTrue) { + unlink_enoent = true; + EXPECT_TRUE(remove_archive("/tmp/nonexistent.tgz")); +} + +TEST_F(CleanupManagerTest, RemoveArchive_PermissionDenied_ReturnsFalse) { + unlink_fail = true; + EXPECT_FALSE(remove_archive("/tmp/protected.tgz")); +} + +// ==================== cleanup_temp_dirs TESTS ==================== + +TEST_F(CleanupManagerTest, CleanupTempDirs_NullCtx) { + EXPECT_FALSE(cleanup_temp_dirs(nullptr, nullptr)); +} + +TEST_F(CleanupManagerTest, CleanupTempDirs_Success) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + + unlink_fail = false; + EXPECT_TRUE(cleanup_temp_dirs(&ctx, &session)); +} + +TEST_F(CleanupManagerTest, CleanupTempDirs_FilesNotExist_StillSucceeds) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + + unlink_enoent = true; + EXPECT_TRUE(cleanup_temp_dirs(&ctx, &session)); +} + +TEST_F(CleanupManagerTest, CleanupTempDirs_UnlinkFails_ReturnsFalse) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + + unlink_fail = true; + EXPECT_FALSE(cleanup_temp_dirs(&ctx, &session)); +} + +// ==================== create_block_marker TESTS ==================== + +TEST_F(CleanupManagerTest, CreateBlockMarker_DirectPath_Success) { + fopen_fail = false; + EXPECT_TRUE(create_block_marker(PATH_DIRECT, 3600)); + EXPECT_EQ(fopen_call_count, 1); +} + +TEST_F(CleanupManagerTest, CreateBlockMarker_CodebigPath_Success) { + fopen_fail = false; + EXPECT_TRUE(create_block_marker(PATH_CODEBIG, 1800)); + EXPECT_EQ(fopen_call_count, 1); +} + +TEST_F(CleanupManagerTest, CreateBlockMarker_InvalidPath_ReturnsFalse) { + EXPECT_FALSE(create_block_marker(PATH_NONE, 3600)); +} + +TEST_F(CleanupManagerTest, CreateBlockMarker_FopenFails_ReturnsFalse) { + fopen_fail = true; + EXPECT_FALSE(create_block_marker(PATH_DIRECT, 3600)); +} + +// ==================== update_block_markers TESTS ==================== + +TEST_F(CleanupManagerTest, UpdateBlockMarkers_NullCtx) { + SessionState session; + memset(&session, 0, sizeof(session)); + // Should not crash + update_block_markers(nullptr, &session); +} + +TEST_F(CleanupManagerTest, UpdateBlockMarkers_NullSession) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + // Should not crash + update_block_markers(&ctx, nullptr); +} + +TEST_F(CleanupManagerTest, UpdateBlockMarkers_SuccessViaCodebig_BlocksDirect) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + session.success = true; + session.used_fallback = true; + session.codebig_attempts = 1; + + fopen_fail = false; + fopen_call_count = 0; + update_block_markers(&ctx, &session); + // Should have created a block marker for Direct path + EXPECT_GE(fopen_call_count, 1); +} + +TEST_F(CleanupManagerTest, UpdateBlockMarkers_DirectSuccess_NoBlocking) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + session.success = true; + session.used_fallback = false; + session.codebig_attempts = 0; + + fopen_call_count = 0; + update_block_markers(&ctx, &session); + // Direct success should not create any block markers + EXPECT_EQ(fopen_call_count, 0); +} + +TEST_F(CleanupManagerTest, UpdateBlockMarkers_CodebigFailed_BlocksCodebig) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + session.success = false; + session.codebig_attempts = 2; + + fopen_fail = false; + fopen_call_count = 0; + update_block_markers(&ctx, &session); + // Should block CodeBig path + EXPECT_GE(fopen_call_count, 1); +} + +// ==================== enforce_privacy TESTS ==================== + +TEST_F(CleanupManagerTest, EnforcePrivacy_NullPath) { + // Should not crash + enforce_privacy(nullptr); +} + +TEST_F(CleanupManagerTest, EnforcePrivacy_DirNotExists) { + ON_CALL(*g_mockFileOperations, dir_exists(_)).WillByDefault(Return(false)); + // Should return early without crash + enforce_privacy("/nonexistent"); +} + +TEST_F(CleanupManagerTest, EnforcePrivacy_OpendirFails) { + ON_CALL(*g_mockFileOperations, dir_exists(_)).WillByDefault(Return(true)); + opendir_fail = true; + // Should return without crash + enforce_privacy(test_log_path); +} + +TEST_F(CleanupManagerTest, EnforcePrivacy_TruncatesFiles) { + ON_CALL(*g_mockFileOperations, dir_exists(_)).WillByDefault(Return(true)); + opendir_fail = false; + + enforce_privacy(test_log_path); + // Real open() will fail on non-existent mock paths; verifies no crash +} + +TEST_F(CleanupManagerTest, EnforcePrivacy_OpenFails_ContinuesOtherFiles) { + ON_CALL(*g_mockFileOperations, dir_exists(_)).WillByDefault(Return(true)); + opendir_fail = false; + + // Real open() will fail on mock paths; function should handle gracefully + enforce_privacy(test_log_path); +} + +// ==================== finalize TESTS ==================== + +TEST_F(CleanupManagerTest, Finalize_NullCtx) { + SessionState session; + memset(&session, 0, sizeof(session)); + // Should not crash + finalize(nullptr, &session); +} + +TEST_F(CleanupManagerTest, Finalize_NullSession) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + // Should not crash + finalize(&ctx, nullptr); +} + +TEST_F(CleanupManagerTest, Finalize_SuccessfulUpload_RemovesArchive) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + session.success = true; + strcpy(session.archive_file, "/tmp/test.tgz"); + + unlink_fail = false; + unlink_call_count = 0; + finalize(&ctx, &session); + // Should call unlink for the archive file and temp files + EXPECT_GT(unlink_call_count, 0); +} + +TEST_F(CleanupManagerTest, Finalize_FailedUpload_NoArchiveRemoval) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + SessionState session; + memset(&session, 0, sizeof(session)); + session.success = false; + strcpy(session.archive_file, "/tmp/test.tgz"); + + unlink_call_count = 0; + finalize(&ctx, &session); + // Should still cleanup temp dirs but not explicitly fail + // (unlink is called for temp files regardless) +} + +TEST_F(CleanupManagerTest, Finalize_MemcaptureTrigger_SkipsArchiveRemoval) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.trigger_type = TRIGGER_MEMCAPTURE; + SessionState session; + memset(&session, 0, sizeof(session)); + session.success = true; + strcpy(session.archive_file, "/tmp/test.tgz"); + + unlink_call_count = 0; + finalize(&ctx, &session); + // TRIGGER_MEMCAPTURE skips archive removal; only temp dirs are cleaned + // unlink_call_count should only be for temp files (2) + EXPECT_LE(unlink_call_count, 2); +} + +// ==================== remove_directory_recursive TESTS (static accessor) ==================== + +class RemoveDirectoryRecursiveTest : public ::testing::Test { +protected: + void SetUp() override { + g_mockFileOperations = new MockFileOperations(); + opendir_fail = false; + remove_fail = false; + mock_readdir_count = 0; + total_opendir_calls = 0; + unlink_fail = false; + + fnRemoveDirectoryRecursive = getRemoveDirectoryRecursive(); + ASSERT_NE(nullptr, fnRemoveDirectoryRecursive); + } + void TearDown() override { + delete g_mockFileOperations; + g_mockFileOperations = nullptr; + } + int (*fnRemoveDirectoryRecursive)(const char*); +}; + +TEST_F(RemoveDirectoryRecursiveTest, NullPath_OpendirFails_CallsRemove) { + opendir_fail = true; + // When opendir fails, falls back to remove() + int result = fnRemoveDirectoryRecursive("/tmp/somefile"); + EXPECT_EQ(0, result); +} + +TEST_F(RemoveDirectoryRecursiveTest, OpendirFails_RemoveFails) { + opendir_fail = true; + remove_fail = true; + int result = fnRemoveDirectoryRecursive("/tmp/somefile"); + EXPECT_EQ(-1, result); +} + +TEST_F(RemoveDirectoryRecursiveTest, EmptyDirectory_RmdirsSuccessfully) { + opendir_fail = false; + remove_fail = false; + // readdir will return . and .. then NULL for empty directory + int result = fnRemoveDirectoryRecursive("/tmp/emptydir"); + EXPECT_EQ(0, result); +} + +TEST_F(RemoveDirectoryRecursiveTest, DirectoryWithFiles_RemovesAll) { + opendir_fail = false; + remove_fail = false; + unlink_fail = false; + int result = fnRemoveDirectoryRecursive("/tmp/testdir"); + EXPECT_EQ(0, result); +} + +TEST_F(RemoveDirectoryRecursiveTest, RmdirFails_ReturnsError) { + opendir_fail = false; + // rmdir is mocked with remove_fail + remove_fail = true; + int result = fnRemoveDirectoryRecursive("/tmp/testdir"); + EXPECT_EQ(-1, result); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/uploadstblogs/unittest/file_operations_gtest.cpp b/uploadstblogs/unittest/file_operations_gtest.cpp new file mode 100644 index 00000000..27b23b1f --- /dev/null +++ b/uploadstblogs/unittest/file_operations_gtest.cpp @@ -0,0 +1,692 @@ +/** + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GTEST_ENABLE +#define RDK_LOG(level, module, ...) do {} while(0) +#endif + +#include "uploadstblogs_types.h" + +#ifndef RDK_API_SUCCESS +#define RDK_API_SUCCESS 0 +#endif +#ifndef RDK_API_FAILURE +#define RDK_API_FAILURE -1 +#endif + +// Mock common_utilities functions used by file_operations.c +extern "C" { + +static bool g_filePresentCheck_result = true; +static bool g_folderCheck_result = true; +static bool g_createDir_result = true; +static bool g_removeFile_result = true; +static bool g_emptyFolder_result = true; +static bool g_copyFiles_result = true; +static int g_getFileSize_result = 1024; + +int filePresentCheck(const char* filepath) { + return g_filePresentCheck_result ? RDK_API_SUCCESS : RDK_API_FAILURE; +} + +int folderCheck(char* dirpath) { + return g_folderCheck_result ? 1 : 0; +} + +int createDir(char* dirpath) { + // Actually create the dir for integration tests + if (g_createDir_result) { + mkdir(dirpath, 0755); + return RDK_API_SUCCESS; + } + return RDK_API_FAILURE; +} + +int removeFile(char* filepath) { + if (g_removeFile_result) { + unlink(filepath); + return RDK_API_SUCCESS; + } + return RDK_API_FAILURE; +} + +int emptyFolder(char* dirpath) { + if (!g_emptyFolder_result) return RDK_API_FAILURE; + // Simple mock: remove files in dir + DIR* dir = opendir(dirpath); + if (!dir) return RDK_API_FAILURE; + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + char path[512]; + snprintf(path, sizeof(path), "%s/%s", dirpath, entry->d_name); + unlink(path); + } + closedir(dir); + return RDK_API_SUCCESS; +} + +int copyFiles(char* src, char* dest) { + return g_copyFiles_result ? RDK_API_SUCCESS : RDK_API_FAILURE; +} + +int getFileSize(const char* filepath) { + return g_getFileSize_result; +} + +} // extern "C" + +// Prevent system_utils.h from being included +#ifndef SYSTEM_UTILS_H +#define SYSTEM_UTILS_H +#endif + +#include "file_operations.h" +#include "../src/file_operations.c" + +using namespace testing; + +class FileOperationsTest : public ::testing::Test { +protected: + void SetUp() override { + g_filePresentCheck_result = true; + g_folderCheck_result = true; + g_createDir_result = true; + g_removeFile_result = true; + g_emptyFolder_result = true; + g_copyFiles_result = true; + g_getFileSize_result = 1024; + + test_dir = std::string("/tmp/fileops_test_") + std::to_string(getpid()); + mkdir(test_dir.c_str(), 0755); + } + + void TearDown() override { + std::string cmd = "rm -rf " + test_dir; + system(cmd.c_str()); + } + + void CreateFile(const char* name, const char* content = "test") { + std::string path = test_dir + "/" + name; + FILE* fp = fopen(path.c_str(), "w"); + if (fp) { fprintf(fp, "%s", content); fclose(fp); } + } + + std::string test_dir; +}; + +// ==================== file_exists TESTS ==================== + +TEST_F(FileOperationsTest, FileExists_NullPath) { + EXPECT_FALSE(file_exists(nullptr)); +} + +TEST_F(FileOperationsTest, FileExists_EmptyPath) { + EXPECT_FALSE(file_exists("")); +} + +TEST_F(FileOperationsTest, FileExists_Success) { + g_filePresentCheck_result = true; + EXPECT_TRUE(file_exists("/tmp/some_file")); +} + +TEST_F(FileOperationsTest, FileExists_NotFound) { + g_filePresentCheck_result = false; + EXPECT_FALSE(file_exists("/tmp/missing")); +} + +// ==================== dir_exists TESTS ==================== + +TEST_F(FileOperationsTest, DirExists_NullPath) { + EXPECT_FALSE(dir_exists(nullptr)); +} + +TEST_F(FileOperationsTest, DirExists_EmptyPath) { + EXPECT_FALSE(dir_exists("")); +} + +TEST_F(FileOperationsTest, DirExists_Success) { + g_folderCheck_result = true; + EXPECT_TRUE(dir_exists("/tmp")); +} + +TEST_F(FileOperationsTest, DirExists_NotFound) { + g_folderCheck_result = false; + EXPECT_FALSE(dir_exists("/nonexistent")); +} + +// ==================== join_path TESTS ==================== + +TEST_F(FileOperationsTest, JoinPath_NullBuffer) { + EXPECT_FALSE(join_path(nullptr, 256, "/tmp", "file.txt")); +} + +TEST_F(FileOperationsTest, JoinPath_NullDir) { + char buf[256]; + EXPECT_FALSE(join_path(buf, sizeof(buf), nullptr, "file.txt")); +} + +TEST_F(FileOperationsTest, JoinPath_NullFilename) { + char buf[256]; + EXPECT_FALSE(join_path(buf, sizeof(buf), "/tmp", nullptr)); +} + +TEST_F(FileOperationsTest, JoinPath_Success) { + char buf[256]; + EXPECT_TRUE(join_path(buf, sizeof(buf), "/tmp", "file.txt")); + EXPECT_STREQ("/tmp/file.txt", buf); +} + +TEST_F(FileOperationsTest, JoinPath_TrailingSlash) { + char buf[256]; + EXPECT_TRUE(join_path(buf, sizeof(buf), "/tmp/", "file.txt")); + EXPECT_STREQ("/tmp/file.txt", buf); +} + +TEST_F(FileOperationsTest, JoinPath_BufferTooSmall) { + char buf[5]; + EXPECT_FALSE(join_path(buf, sizeof(buf), "/tmp", "file.txt")); +} + +// ==================== create_directory TESTS ==================== + +TEST_F(FileOperationsTest, CreateDirectory_NullPath) { + EXPECT_FALSE(create_directory(nullptr)); +} + +TEST_F(FileOperationsTest, CreateDirectory_EmptyPath) { + EXPECT_FALSE(create_directory("")); +} + +TEST_F(FileOperationsTest, CreateDirectory_AlreadyExists) { + g_folderCheck_result = true; + EXPECT_TRUE(create_directory("/tmp")); +} + +TEST_F(FileOperationsTest, CreateDirectory_CreateFails) { + g_folderCheck_result = false; + g_createDir_result = false; + EXPECT_FALSE(create_directory("/tmp/fail_dir")); +} + +// ==================== remove_file TESTS ==================== + +TEST_F(FileOperationsTest, RemoveFile_NullPath) { + EXPECT_FALSE(remove_file(nullptr)); +} + +TEST_F(FileOperationsTest, RemoveFile_EmptyPath) { + EXPECT_FALSE(remove_file("")); +} + +TEST_F(FileOperationsTest, RemoveFile_FileNotExists) { + g_filePresentCheck_result = false; + EXPECT_TRUE(remove_file("/tmp/already_gone")); +} + +TEST_F(FileOperationsTest, RemoveFile_Success) { + g_filePresentCheck_result = true; + g_removeFile_result = true; + EXPECT_TRUE(remove_file("/tmp/somefile")); +} + +TEST_F(FileOperationsTest, RemoveFile_Failure) { + g_filePresentCheck_result = true; + g_removeFile_result = false; + EXPECT_FALSE(remove_file("/tmp/locked")); +} + +// ==================== remove_directory TESTS ==================== + +TEST_F(FileOperationsTest, RemoveDirectory_NullPath) { + EXPECT_FALSE(remove_directory(nullptr)); +} + +TEST_F(FileOperationsTest, RemoveDirectory_EmptyPath) { + EXPECT_FALSE(remove_directory("")); +} + +TEST_F(FileOperationsTest, RemoveDirectory_NotExists) { + g_folderCheck_result = false; + EXPECT_TRUE(remove_directory("/tmp/gone")); +} + +TEST_F(FileOperationsTest, RemoveDirectory_EmptyFolderFails) { + g_folderCheck_result = true; + g_emptyFolder_result = false; + EXPECT_FALSE(remove_directory("/tmp/stuck")); +} + +// ==================== copy_file TESTS ==================== + +TEST_F(FileOperationsTest, CopyFile_NullSrc) { + EXPECT_FALSE(copy_file(nullptr, "/tmp/dest")); +} + +TEST_F(FileOperationsTest, CopyFile_NullDest) { + EXPECT_FALSE(copy_file("/tmp/src", nullptr)); +} + +TEST_F(FileOperationsTest, CopyFile_EmptySrc) { + EXPECT_FALSE(copy_file("", "/tmp/dest")); +} + +TEST_F(FileOperationsTest, CopyFile_EmptyDest) { + EXPECT_FALSE(copy_file("/tmp/src", "")); +} + +TEST_F(FileOperationsTest, CopyFile_Success) { + g_copyFiles_result = true; + EXPECT_TRUE(copy_file("/tmp/src", "/tmp/dest")); +} + +TEST_F(FileOperationsTest, CopyFile_Failure) { + g_copyFiles_result = false; + EXPECT_FALSE(copy_file("/tmp/src", "/tmp/dest")); +} + +// ==================== get_file_size TESTS ==================== + +TEST_F(FileOperationsTest, GetFileSize_NullPath) { + EXPECT_EQ(-1, get_file_size(nullptr)); +} + +TEST_F(FileOperationsTest, GetFileSize_EmptyPath) { + EXPECT_EQ(-1, get_file_size("")); +} + +TEST_F(FileOperationsTest, GetFileSize_Success) { + g_getFileSize_result = 2048; + EXPECT_EQ(2048L, get_file_size("/tmp/test")); +} + +TEST_F(FileOperationsTest, GetFileSize_Error) { + g_getFileSize_result = -1; + EXPECT_EQ(-1L, get_file_size("/tmp/missing")); +} + +// ==================== is_directory_empty TESTS ==================== + +TEST_F(FileOperationsTest, IsDirEmpty_NullPath) { + EXPECT_FALSE(is_directory_empty(nullptr)); +} + +TEST_F(FileOperationsTest, IsDirEmpty_EmptyPath) { + EXPECT_FALSE(is_directory_empty("")); +} + +TEST_F(FileOperationsTest, IsDirEmpty_DirNotExists) { + g_folderCheck_result = false; + EXPECT_FALSE(is_directory_empty("/nonexistent")); +} + +TEST_F(FileOperationsTest, IsDirEmpty_EmptyDir) { + g_folderCheck_result = true; + EXPECT_TRUE(is_directory_empty(test_dir.c_str())); +} + +TEST_F(FileOperationsTest, IsDirEmpty_NonEmptyDir) { + g_folderCheck_result = true; + CreateFile("something.txt"); + EXPECT_FALSE(is_directory_empty(test_dir.c_str())); +} + +// ==================== has_log_files TESTS ==================== + +TEST_F(FileOperationsTest, HasLogFiles_NullPath) { + EXPECT_FALSE(has_log_files(nullptr)); +} + +TEST_F(FileOperationsTest, HasLogFiles_EmptyPath) { + EXPECT_FALSE(has_log_files("")); +} + +TEST_F(FileOperationsTest, HasLogFiles_DirNotExists) { + g_folderCheck_result = false; + EXPECT_FALSE(has_log_files("/nonexistent")); +} + +TEST_F(FileOperationsTest, HasLogFiles_NoLogFiles) { + g_folderCheck_result = true; + CreateFile("data.bin"); + CreateFile("config.conf"); + EXPECT_FALSE(has_log_files(test_dir.c_str())); +} + +TEST_F(FileOperationsTest, HasLogFiles_HasTxtFile) { + g_folderCheck_result = true; + CreateFile("system.txt"); + EXPECT_TRUE(has_log_files(test_dir.c_str())); +} + +TEST_F(FileOperationsTest, HasLogFiles_HasLogFile) { + g_folderCheck_result = true; + CreateFile("messages.log"); + EXPECT_TRUE(has_log_files(test_dir.c_str())); +} + +// ==================== write_file TESTS ==================== + +TEST_F(FileOperationsTest, WriteFile_NullPath) { + EXPECT_FALSE(write_file(nullptr, "content")); +} + +TEST_F(FileOperationsTest, WriteFile_EmptyPath) { + EXPECT_FALSE(write_file("", "content")); +} + +TEST_F(FileOperationsTest, WriteFile_NullContent) { + EXPECT_FALSE(write_file("/tmp/test", nullptr)); +} + +TEST_F(FileOperationsTest, WriteFile_Success) { + std::string path = test_dir + "/write_test.txt"; + EXPECT_TRUE(write_file(path.c_str(), "hello world")); + + FILE* fp = fopen(path.c_str(), "r"); + ASSERT_NE(nullptr, fp); + char buf[64] = {0}; + fgets(buf, sizeof(buf), fp); + fclose(fp); + EXPECT_STREQ("hello world", buf); +} + +// ==================== read_file TESTS ==================== + +TEST_F(FileOperationsTest, ReadFile_NullPath) { + char buf[64]; + EXPECT_EQ(-1, read_file(nullptr, buf, sizeof(buf))); +} + +TEST_F(FileOperationsTest, ReadFile_NullBuffer) { + EXPECT_EQ(-1, read_file("/tmp/file", nullptr, 64)); +} + +TEST_F(FileOperationsTest, ReadFile_ZeroBufferSize) { + char buf[64]; + EXPECT_EQ(-1, read_file("/tmp/file", buf, 0)); +} + +TEST_F(FileOperationsTest, ReadFile_Success) { + std::string path = test_dir + "/read_test.txt"; + FILE* fp = fopen(path.c_str(), "w"); + ASSERT_NE(nullptr, fp); + fprintf(fp, "test content"); + fclose(fp); + + char buf[64] = {0}; + int bytes = read_file(path.c_str(), buf, sizeof(buf)); + EXPECT_GT(bytes, 0); + EXPECT_STREQ("test content", buf); +} + +TEST_F(FileOperationsTest, ReadFile_FileNotFound) { + char buf[64]; + EXPECT_EQ(-1, read_file("/tmp/nonexistent_xyz_abc", buf, sizeof(buf))); +} + +// ==================== add_timestamp_to_files TESTS ==================== + +TEST_F(FileOperationsTest, AddTimestamp_NullPath) { + EXPECT_EQ(-1, add_timestamp_to_files(nullptr)); +} + +TEST_F(FileOperationsTest, AddTimestamp_DirNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(-1, add_timestamp_to_files("/nonexistent")); +} + +TEST_F(FileOperationsTest, AddTimestamp_EmptyDir) { + g_folderCheck_result = true; + EXPECT_EQ(0, add_timestamp_to_files(test_dir.c_str())); +} + +TEST_F(FileOperationsTest, AddTimestamp_RenamesFiles) { + g_folderCheck_result = true; + CreateFile("app.log"); + CreateFile("system.txt"); + + EXPECT_EQ(0, add_timestamp_to_files(test_dir.c_str())); + + // Verify original files are gone (renamed) + std::string orig1 = test_dir + "/app.log"; + std::string orig2 = test_dir + "/system.txt"; + EXPECT_NE(0, access(orig1.c_str(), F_OK)); + EXPECT_NE(0, access(orig2.c_str(), F_OK)); +} + +TEST_F(FileOperationsTest, AddTimestamp_SkipsBakFiles) { + g_folderCheck_result = true; + CreateFile("bak1_something"); + CreateFile("bak2_other"); + CreateFile("normal.log"); + + EXPECT_EQ(0, add_timestamp_to_files(test_dir.c_str())); + + // bak files should still exist with original names + std::string bak1 = test_dir + "/bak1_something"; + std::string bak2 = test_dir + "/bak2_other"; + EXPECT_EQ(0, access(bak1.c_str(), F_OK)); + EXPECT_EQ(0, access(bak2.c_str(), F_OK)); +} + +// ==================== remove_timestamp_from_files TESTS ==================== + +TEST_F(FileOperationsTest, RemoveTimestamp_NullPath) { + EXPECT_EQ(-1, remove_timestamp_from_files(nullptr)); +} + +TEST_F(FileOperationsTest, RemoveTimestamp_DirNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(-1, remove_timestamp_from_files("/nonexistent")); +} + +TEST_F(FileOperationsTest, RemoveTimestamp_RoundTrip) { + g_folderCheck_result = true; + CreateFile("test.log"); + CreateFile("app.txt"); + + // Add timestamps + EXPECT_EQ(0, add_timestamp_to_files(test_dir.c_str())); + + // Remove timestamps + EXPECT_EQ(0, remove_timestamp_from_files(test_dir.c_str())); + + // Original filenames should be restored + std::string f1 = test_dir + "/test.log"; + std::string f2 = test_dir + "/app.txt"; + EXPECT_EQ(0, access(f1.c_str(), F_OK)); + EXPECT_EQ(0, access(f2.c_str(), F_OK)); +} + +// ==================== add_timestamp_to_files_uploadlogsnow TESTS ==================== + +TEST_F(FileOperationsTest, AddTimestampULN_NullPath) { + EXPECT_EQ(-1, add_timestamp_to_files_uploadlogsnow(nullptr)); +} + +TEST_F(FileOperationsTest, AddTimestampULN_DirNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(-1, add_timestamp_to_files_uploadlogsnow("/nonexistent")); +} + +TEST_F(FileOperationsTest, AddTimestampULN_SkipsRebootLog) { + g_folderCheck_result = true; + CreateFile("reboot.log"); + CreateFile("normal.log"); + + EXPECT_EQ(0, add_timestamp_to_files_uploadlogsnow(test_dir.c_str())); + + // reboot.log should remain unchanged + std::string reboot = test_dir + "/reboot.log"; + EXPECT_EQ(0, access(reboot.c_str(), F_OK)); + + // normal.log should be renamed + std::string normal = test_dir + "/normal.log"; + EXPECT_NE(0, access(normal.c_str(), F_OK)); +} + +TEST_F(FileOperationsTest, AddTimestampULN_SkipsABLReason) { + g_folderCheck_result = true; + CreateFile("ABLReason.txt"); + CreateFile("app.log"); + + EXPECT_EQ(0, add_timestamp_to_files_uploadlogsnow(test_dir.c_str())); + + std::string abl = test_dir + "/ABLReason.txt"; + EXPECT_EQ(0, access(abl.c_str(), F_OK)); +} + +TEST_F(FileOperationsTest, AddTimestampULN_SkipsAlreadyTimestamped) { + g_folderCheck_result = true; + // File with existing AM/PM timestamp pattern + CreateFile("01-01-25-10-30AM-app.log"); + + EXPECT_EQ(0, add_timestamp_to_files_uploadlogsnow(test_dir.c_str())); + + // Should remain unchanged + std::string ts_file = test_dir + "/01-01-25-10-30AM-app.log"; + EXPECT_EQ(0, access(ts_file.c_str(), F_OK)); +} + +// ==================== move_directory_contents TESTS ==================== + +TEST_F(FileOperationsTest, MoveDirectoryContents_NullSrc) { + EXPECT_EQ(-1, move_directory_contents(nullptr, "/tmp/dest")); +} + +TEST_F(FileOperationsTest, MoveDirectoryContents_NullDest) { + EXPECT_EQ(-1, move_directory_contents("/tmp/src", nullptr)); +} + +TEST_F(FileOperationsTest, MoveDirectoryContents_SrcNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(-1, move_directory_contents("/nonexistent", "/tmp/dest")); +} + +TEST_F(FileOperationsTest, MoveDirectoryContents_Success) { + g_folderCheck_result = true; + CreateFile("moveme.log"); + + std::string dest = test_dir + "_dest"; + mkdir(dest.c_str(), 0755); + + EXPECT_EQ(0, move_directory_contents(test_dir.c_str(), dest.c_str())); + + // File should be in dest now + std::string moved = dest + "/moveme.log"; + EXPECT_EQ(0, access(moved.c_str(), F_OK)); + + // Cleanup dest + std::string cmd = "rm -rf " + dest; + system(cmd.c_str()); +} + +// ==================== clean_directory TESTS ==================== + +TEST_F(FileOperationsTest, CleanDirectory_NullPath) { + EXPECT_EQ(-1, clean_directory(nullptr)); +} + +TEST_F(FileOperationsTest, CleanDirectory_DirNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(-1, clean_directory("/nonexistent")); +} + +TEST_F(FileOperationsTest, CleanDirectory_Success) { + g_folderCheck_result = true; + g_emptyFolder_result = true; + CreateFile("junk.tmp"); + + EXPECT_EQ(0, clean_directory(test_dir.c_str())); +} + +TEST_F(FileOperationsTest, CleanDirectory_Failure) { + g_folderCheck_result = true; + g_emptyFolder_result = false; + EXPECT_EQ(-1, clean_directory(test_dir.c_str())); +} + +// ==================== clear_old_packet_captures TESTS ==================== + +TEST_F(FileOperationsTest, ClearPacketCaptures_NullPath) { + EXPECT_EQ(-1, clear_old_packet_captures(nullptr)); +} + +TEST_F(FileOperationsTest, ClearPacketCaptures_DirNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(-1, clear_old_packet_captures("/nonexistent")); +} + +TEST_F(FileOperationsTest, ClearPacketCaptures_RemovesPcapFiles) { + g_folderCheck_result = true; + g_filePresentCheck_result = true; + g_removeFile_result = true; + CreateFile("capture.pcap"); + CreateFile("other.pcap"); + CreateFile("keep.log"); + + EXPECT_EQ(0, clear_old_packet_captures(test_dir.c_str())); + + // .pcap files should be removed, .log should remain + std::string pcap1 = test_dir + "/capture.pcap"; + std::string pcap2 = test_dir + "/other.pcap"; + std::string log = test_dir + "/keep.log"; + EXPECT_NE(0, access(pcap1.c_str(), F_OK)); + EXPECT_NE(0, access(pcap2.c_str(), F_OK)); + EXPECT_EQ(0, access(log.c_str(), F_OK)); +} + +TEST_F(FileOperationsTest, ClearPacketCaptures_NoPcapFiles) { + g_folderCheck_result = true; + CreateFile("data.log"); + EXPECT_EQ(0, clear_old_packet_captures(test_dir.c_str())); +} + +// ==================== remove_old_directories TESTS ==================== + +TEST_F(FileOperationsTest, RemoveOldDirs_NullBasePath) { + EXPECT_EQ(-1, remove_old_directories(nullptr, "pattern", 3)); +} + +TEST_F(FileOperationsTest, RemoveOldDirs_NullPattern) { + EXPECT_EQ(-1, remove_old_directories("/tmp", nullptr, 3)); +} + +TEST_F(FileOperationsTest, RemoveOldDirs_NegativeDays) { + EXPECT_EQ(-1, remove_old_directories("/tmp", "pat", -1)); +} + +TEST_F(FileOperationsTest, RemoveOldDirs_BaseNotExists) { + g_folderCheck_result = false; + EXPECT_EQ(0, remove_old_directories("/nonexistent", "pat", 3)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/uploadstblogs/unittest/strategies_gtest.cpp b/uploadstblogs/unittest/strategies_gtest.cpp index ea22c818..ad299005 100755 --- a/uploadstblogs/unittest/strategies_gtest.cpp +++ b/uploadstblogs/unittest/strategies_gtest.cpp @@ -104,6 +104,7 @@ bool copy_file(const char* src, const char* dest) { // Mock implementations for external functions static bool g_mock_dir_exists = true; +static bool g_mock_create_directory_result = true; static int g_mock_add_timestamp_result = 0; static int g_mock_collect_pcap_result = 0; static int g_mock_create_archive_result = 0; @@ -456,7 +457,7 @@ extern "C" { if (g_mock_file_ops) { return g_mock_file_ops->create_directory(dirpath); } - return true; // Success + return g_mock_create_directory_result; } bool remove_directory(const char* dirpath) { @@ -1593,6 +1594,1621 @@ TEST_F(HelperFunctionsTest, WaitForTelemetryPrevlogsDone_ShortTimeoutInTest) { EXPECT_LT(std::chrono::duration_cast(elapsed).count(), 5); } +// ==================== STATIC FUNCTION ACCESSOR TESTS ==================== +// Tests for static functions exposed via #ifdef GTEST_ENABLE accessor pattern +// (same approach as dcm_parseconf.c) + + + int (*getGetJsonRpc(void))(char *, DownloadData *); + bool (*getReadDcmUploadFlag(void))(void); + int (*getDcmSetup(void))(RuntimeContext*, SessionState*); + int (*getDcmArchive(void))(RuntimeContext*, SessionState*); + int (*getDcmUpload(void))(RuntimeContext*, SessionState*); + int (*getDcmCleanup(void))(RuntimeContext*, SessionState*, bool); + int (*getOndemandSetup(void))(RuntimeContext*, SessionState*); + int (*getOndemandArchive(void))(RuntimeContext*, SessionState*); + int (*getOndemandUpload(void))(RuntimeContext*, SessionState*); + int (*getOndemandCleanup(void))(RuntimeContext*, SessionState*, bool); + int (*getRebootSetup(void))(RuntimeContext*, SessionState*); + int (*getRebootArchive(void))(RuntimeContext*, SessionState*); + int (*getRebootUpload(void))(RuntimeContext*, SessionState*); + int (*getRebootCleanup(void))(RuntimeContext*, SessionState*, bool); + int (*getCopyOptLogsFiles(void))(const char*, const char*); + int (*getCopyDirRecursive(void))(const char*, const char*); + int (*getCopyAllFilesToDcm(void))(const char*, const char*); + int (*getProcessDcmUploadList(void))(RuntimeContext*); + + +// ---- read_dcm_upload_flag tests ---- + +/** + * Test fixture for read_dcm_upload_flag static function. + */ +class ReadDcmUploadFlagTest : public ::testing::Test { +protected: + void SetUp() override { + g_mock_file_ops = nullptr; + fnReadDcmUploadFlag = getReadDcmUploadFlag(); + ASSERT_NE(nullptr, fnReadDcmUploadFlag); + } + + void TearDown() override { + g_mock_file_ops = nullptr; + } + + /** + * Create a temp file with given content and return a real FILE* for reading. + * Caller must fclose the returned FILE* after use. + */ + FILE* CreateTempFileWithContent(const char* content) { + char temp_file[64]; + snprintf(temp_file, sizeof(temp_file), "/tmp/dcm_upload_flag_test_%d", getpid()); + int fd = open(temp_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (fd < 0) return nullptr; + if (content && strlen(content) > 0) { + write(fd, content, strlen(content)); + } + close(fd); + + fd = open(temp_file, O_RDONLY); + if (fd < 0) return nullptr; + FILE* fp = fdopen(fd, "r"); + // Unlink now (file stays open until fclose) + unlink(temp_file); + return fp; + } + + bool (*fnReadDcmUploadFlag)(void); +}; + +/** + * @test read_dcm_upload_flag returns true when DCMSettings.conf does not exist. + * Covers: fopen returns NULL → default to upload enabled (true). + */ +TEST_F(ReadDcmUploadFlagTest, FileNotFound_DefaultsToTrue) { + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(nullptr)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_TRUE(result); +} + +/** + * @test read_dcm_upload_flag returns true when upload key has value "true". + * Covers: Key found with value "true" → returns true. + */ +TEST_F(ReadDcmUploadFlagTest, UploadTrue_ReturnsTrue) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload=true\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_TRUE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag returns false when upload key has value "false". + * Covers: Key found with value "false" → returns false. + */ +TEST_F(ReadDcmUploadFlagTest, UploadFalse_ReturnsFalse) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload=false\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_FALSE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag returns true with case-insensitive "TRUE". + * Covers: strncasecmp handles uppercase value → returns true. + */ +TEST_F(ReadDcmUploadFlagTest, UploadTRUE_CaseInsensitive_ReturnsTrue) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload=TRUE\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_TRUE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag returns true with quoted value "true". + * Covers: Leading quote stripped before comparison → returns true. + */ +TEST_F(ReadDcmUploadFlagTest, UploadQuotedTrue_ReturnsTrue) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload=\"true\"\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_TRUE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag returns false when key is not found in file. + * Covers: fgets exhausts file without finding key → returns false (upload_enabled stays false). + */ +TEST_F(ReadDcmUploadFlagTest, KeyNotFound_ReturnsFalse) { + FILE* real_fp = CreateTempFileWithContent( + "some:other:setting=value\nanother:setting=123\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_FALSE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag returns false when value after '=' is empty. + * Covers: equals++ points to whitespace/end → strncasecmp does not match "true". + */ +TEST_F(ReadDcmUploadFlagTest, EmptyValue_ReturnsFalse) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload=\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_FALSE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag returns false when key line has no '=' delimiter. + * Covers: strchr(line, '=') returns NULL → upload_enabled stays false. + */ +TEST_F(ReadDcmUploadFlagTest, NoEqualsDelimiter_ReturnsFalse) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload true\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_FALSE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag handles key among multiple lines. + * Covers: Loop iterates past non-matching lines to find the key. + */ +TEST_F(ReadDcmUploadFlagTest, KeyFoundAmongMultipleLines_ReturnsTrue) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:frequency=24\n" + "urn:settings:LogUploadSettings:protocol=HTTPS\n" + "urn:settings:LogUploadSettings:upload=true\n" + "urn:settings:FirmwareSettings:cron=0 4 * * *\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_TRUE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test read_dcm_upload_flag handles value with leading whitespace. + * Covers: isspace() skip loop before comparison. + */ +TEST_F(ReadDcmUploadFlagTest, ValueWithLeadingWhitespace_ReturnsTrue) { + FILE* real_fp = CreateTempFileWithContent( + "urn:settings:LogUploadSettings:upload= true\n"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + bool result = fnReadDcmUploadFlag(); + EXPECT_TRUE(result); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +// ---- getJsonRpc accessor tests ---- + +/** + * Test fixture for getJsonRpc static function accessed via accessor. + */ +class GetJsonRpcAccessorTest : public ::testing::Test { +protected: + void SetUp() override { + g_mock_file_ops = nullptr; + g_mock_allocDowndLoadDataMem_result = 0; + g_mock_doCurlInit_result = (void *)0xCAFE; + g_mock_getJsonRpcData_result = 0; + memset(g_mock_jsonrpc_response, 0, sizeof(g_mock_jsonrpc_response)); + g_mock_doCurlInit_call_count = 0; + g_mock_getJsonRpcData_call_count = 0; + g_mock_doStopDownload_call_count = 0; + + fnGetJsonRpc = getGetJsonRpc(); + ASSERT_NE(nullptr, fnGetJsonRpc); + } + + void TearDown() override { + g_mock_file_ops = nullptr; + } + + int (*fnGetJsonRpc)(char *, DownloadData *); +}; + +/** + * @test getJsonRpc via accessor: success path with valid response. + * Covers: Normal flow through accessor function pointer. + */ +TEST_F(GetJsonRpcAccessorTest, Success_ViaAccessor) { + DownloadData dwnloc; + dwnloc.pvOut = calloc(1, 1024); + dwnloc.memsize = 1024; + dwnloc.datasize = 0; + + strncpy(g_mock_jsonrpc_response, "{\"result\":\"ok\"}", sizeof(g_mock_jsonrpc_response) - 1); + g_mock_getJsonRpcData_result = 0; + + char post_data[] = "{\"jsonrpc\":\"2.0\",\"method\":\"test\"}"; + int ret = fnGetJsonRpc(post_data, &dwnloc); + + EXPECT_EQ(ret, 0); + EXPECT_EQ(g_mock_doCurlInit_call_count, 1); + EXPECT_EQ(g_mock_getJsonRpcData_call_count, 1); + EXPECT_EQ(g_mock_doStopDownload_call_count, 1); + + free(dwnloc.pvOut); +} + +/** + * @test getJsonRpc via accessor: NULL pvOut returns error. + * Covers: pJsonRpc->pvOut == NULL guard via accessor. + */ +TEST_F(GetJsonRpcAccessorTest, NullPvOut_ReturnsError) { + DownloadData dwnloc; + memset(&dwnloc, 0, sizeof(dwnloc)); + dwnloc.pvOut = NULL; + + char post_data[] = "{\"jsonrpc\":\"2.0\",\"method\":\"test\"}"; + int ret = fnGetJsonRpc(post_data, &dwnloc); + + EXPECT_EQ(ret, -1); + EXPECT_EQ(g_mock_doCurlInit_call_count, 0); +} + +/** + * @test getJsonRpc via accessor: curl init failure. + * Covers: doCurlInit returns NULL via accessor. + */ +TEST_F(GetJsonRpcAccessorTest, CurlInitNull_ReturnsError) { + DownloadData dwnloc; + dwnloc.pvOut = calloc(1, 1024); + dwnloc.memsize = 1024; + dwnloc.datasize = 0; + + g_mock_doCurlInit_result = NULL; + + char post_data[] = "{\"jsonrpc\":\"2.0\",\"method\":\"test\"}"; + int ret = fnGetJsonRpc(post_data, &dwnloc); + + EXPECT_EQ(ret, -1); + EXPECT_EQ(g_mock_getJsonRpcData_call_count, 0); + + free(dwnloc.pvOut); +} + +// ---- DCM strategy phase accessor tests ---- + +/** + * Test fixture for DCM strategy static functions accessed via GTEST_ENABLE accessors. + */ +class DcmStrategyAccessorTest : public ::testing::Test { +protected: + void SetUp() override { + g_mock_file_ops = nullptr; + g_mock_dir_exists = true; + g_mock_create_directory_result = true; + g_mock_add_timestamp_result = 0; + g_mock_collect_pcap_result = 0; + g_mock_create_archive_result = 0; + g_mock_upload_archive_result = 0; + g_mock_clear_packet_captures_result = 0; + g_mock_remove_directory_result = true; + + g_add_timestamp_call_count = 0; + g_collect_pcap_call_count = 0; + g_create_archive_call_count = 0; + g_upload_archive_call_count = 0; + g_clear_packet_captures_call_count = 0; + g_remove_directory_call_count = 0; + g_sleep_call_count = 0; + + memset(&ctx, 0, sizeof(ctx)); + strcpy(ctx.log_path, "/opt/logs"); + strcpy(ctx.dcm_log_path, "/tmp/dcm_logs"); + strcpy(ctx.telemetry_path, "/tmp/telemetry"); + + memset(&session, 0, sizeof(session)); + strcpy(session.archive_file, "test_archive.tar.gz"); + session.success = false; + + fnDcmSetup = getDcmSetup(); + fnDcmArchive = getDcmArchive(); + fnDcmUpload = getDcmUpload(); + fnDcmCleanup = getDcmCleanup(); + } + + void TearDown() override { + g_mock_file_ops = nullptr; + } + + RuntimeContext ctx; + SessionState session; + int (*fnDcmSetup)(RuntimeContext*, SessionState*); + int (*fnDcmArchive)(RuntimeContext*, SessionState*); + int (*fnDcmUpload)(RuntimeContext*, SessionState*); + int (*fnDcmCleanup)(RuntimeContext*, SessionState*, bool); +}; + +/** + * @test dcm_setup via accessor: NULL context returns -1. + * Covers: NULL ctx guard. + */ +TEST_F(DcmStrategyAccessorTest, Setup_NullCtx_ReturnsError) { + int result = fnDcmSetup(nullptr, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test dcm_setup via accessor: DCM_LOG_PATH does not exist returns -1. + * Covers: dir_exists returns false path. + */ +TEST_F(DcmStrategyAccessorTest, Setup_DirNotExists_ReturnsError) { + g_mock_dir_exists = false; + g_mock_create_directory_result = false; + int result = fnDcmSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test dcm_setup via accessor: add_timestamp failure is non-fatal. + * Covers: add_timestamp_to_files returns error but function still returns 0. + */ +TEST_F(DcmStrategyAccessorTest, Setup_TimestampFailure_StillSucceeds) { + g_mock_add_timestamp_result = -1; + int result = fnDcmSetup(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_add_timestamp_call_count, 1); +} + +/** + * @test dcm_archive via accessor: NULL parameters returns -1. + * Covers: NULL ctx/session guard. + */ +TEST_F(DcmStrategyAccessorTest, Archive_NullParams_ReturnsError) { + EXPECT_EQ(fnDcmArchive(nullptr, &session), -1); + EXPECT_EQ(fnDcmArchive(&ctx, nullptr), -1); +} + +/** + * @test dcm_archive via accessor: create_archive failure returns -1. + * Covers: create_archive error path. + */ +TEST_F(DcmStrategyAccessorTest, Archive_CreateArchiveFails_ReturnsError) { + g_mock_create_archive_result = -1; + int result = fnDcmArchive(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test dcm_archive via accessor: PCAP not collected when include_pcap is false. + * Covers: include_pcap == false branch. + */ +TEST_F(DcmStrategyAccessorTest, Archive_NoPcap_SkipsCollect) { + ctx.include_pcap = false; + int result = fnDcmArchive(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_collect_pcap_call_count, 0); +} + +/** + * @test dcm_upload via accessor: NULL parameters returns -1. + * Covers: NULL ctx/session guard. + */ +TEST_F(DcmStrategyAccessorTest, Upload_NullParams_ReturnsError) { + EXPECT_EQ(fnDcmUpload(nullptr, &session), -1); + EXPECT_EQ(fnDcmUpload(&ctx, nullptr), -1); +} + +/** + * @test dcm_upload via accessor: upload failure propagates return code. + * Covers: upload_archive returns non-zero. + */ +TEST_F(DcmStrategyAccessorTest, Upload_UploadFails_ReturnsError) { + g_mock_upload_archive_result = -1; + int result = fnDcmUpload(&ctx, &session); + EXPECT_EQ(result, -1); + EXPECT_FALSE(session.success); +} + +/** + * @test dcm_upload via accessor: PCAP clearing skipped when include_pcap is false. + * Covers: include_pcap == false branch skips clear_old_packet_captures. + */ +TEST_F(DcmStrategyAccessorTest, Upload_NoPcap_SkipsClear) { + ctx.include_pcap = false; + int result = fnDcmUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_clear_packet_captures_call_count, 0); +} + +/** + * @test dcm_cleanup via accessor: NULL context returns -1. + * Covers: NULL ctx guard. + */ +TEST_F(DcmStrategyAccessorTest, Cleanup_NullCtx_ReturnsError) { + int result = fnDcmCleanup(nullptr, &session, true); + EXPECT_EQ(result, -1); +} + +/** + * @test dcm_cleanup via accessor: directory removal failure returns -1. + * Covers: remove_directory returns false. + */ +TEST_F(DcmStrategyAccessorTest, Cleanup_RemoveDirFails_ReturnsError) { + g_mock_remove_directory_result = false; + int result = fnDcmCleanup(&ctx, &session, true); + EXPECT_EQ(result, -1); +} + +/** + * @test dcm_cleanup via accessor: directory does not exist succeeds (no-op). + * Covers: dir_exists returns false → skip removal → success. + */ +TEST_F(DcmStrategyAccessorTest, Cleanup_DirNotExists_Succeeds) { + g_mock_dir_exists = false; + int result = fnDcmCleanup(&ctx, &session, true); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_remove_directory_call_count, 0); +} + +// ---- Ondemand strategy phase accessor tests ---- + +/** + * Test fixture for Ondemand strategy static functions accessed via GTEST_ENABLE accessors. + */ +class OndemandStrategyAccessorTest : public ::testing::Test { +protected: + void SetUp() override { + g_mock_file_ops = &mock_ops; + g_mock_create_archive_result = 0; + g_mock_upload_archive_result = 0; + g_create_archive_call_count = 0; + g_upload_archive_call_count = 0; + g_sleep_call_count = 0; + + memset(&ctx, 0, sizeof(ctx)); + strcpy(ctx.log_path, "/opt/logs"); + strcpy(ctx.telemetry_path, "/tmp/telemetry"); + ctx.flag = true; + + memset(&session, 0, sizeof(session)); + strcpy(session.archive_file, "ondemand_logs.tar.gz"); + session.success = false; + + fnOndemandSetup = getOndemandSetup(); + fnOndemandArchive = getOndemandArchive(); + fnOndemandUpload = getOndemandUpload(); + fnOndemandCleanup = getOndemandCleanup(); + } + + void TearDown() override { + g_mock_file_ops = nullptr; + } + + StrictMock mock_ops; + RuntimeContext ctx; + SessionState session; + int (*fnOndemandSetup)(RuntimeContext*, SessionState*); + int (*fnOndemandArchive)(RuntimeContext*, SessionState*); + int (*fnOndemandUpload)(RuntimeContext*, SessionState*); + int (*fnOndemandCleanup)(RuntimeContext*, SessionState*, bool); +}; + +/** + * @test ondemand_setup via accessor: LOG_PATH does not exist returns -1. + * Covers: dir_exists(ctx->log_path) returns false. + */ +TEST_F(OndemandStrategyAccessorTest, Setup_LogPathNotExists_ReturnsError) { + EXPECT_CALL(mock_ops, dir_exists(StrEq("/opt/logs"))) + .WillOnce(Return(false)); + + int result = fnOndemandSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test ondemand_setup via accessor: no log files returns -1. + * Covers: has_log_files returns false → emit_no_logs_ondemand called. + */ +TEST_F(OndemandStrategyAccessorTest, Setup_NoLogFiles_ReturnsError) { + EXPECT_CALL(mock_ops, dir_exists(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, has_log_files(StrEq("/opt/logs"))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, emit_no_logs_ondemand()); + + int result = fnOndemandSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test ondemand_setup via accessor: create_directory fails returns -1. + * Covers: create_directory(ONDEMAND_TEMP_DIR) returns false. + */ +TEST_F(OndemandStrategyAccessorTest, Setup_CreateDirFails_ReturnsError) { + EXPECT_CALL(mock_ops, dir_exists(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, has_log_files(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, create_directory(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(false)); + + int result = fnOndemandSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test ondemand_setup via accessor: collect_logs returns 0 → failure. + * Covers: collect_logs returns count <= 0. + */ +TEST_F(OndemandStrategyAccessorTest, Setup_CollectLogsReturnsZero_ReturnsError) { + EXPECT_CALL(mock_ops, dir_exists(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, has_log_files(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, create_directory(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, collect_logs(&ctx, &session, StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(0)); + + int result = fnOndemandSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test ondemand_archive via accessor: create_archive failure returns -1. + * Covers: create_archive returns non-zero. + */ +TEST_F(OndemandStrategyAccessorTest, Archive_CreateArchiveFails_ReturnsError) { + EXPECT_CALL(mock_ops, create_archive(&ctx, &session, StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(-1)); + + int result = fnOndemandArchive(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test ondemand_archive via accessor: success includes sleep call. + * Covers: create_archive succeeds → sleep(2) called → return 0. + */ +TEST_F(OndemandStrategyAccessorTest, Archive_Success_SleepsAndReturns) { + EXPECT_CALL(mock_ops, create_archive(&ctx, &session, StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(0)); + EXPECT_CALL(mock_ops, sleep(2u)) + .WillOnce(Return(0)); + + int result = fnOndemandArchive(&ctx, &session); + EXPECT_EQ(result, 0); +} + +/** + * @test ondemand_upload via accessor: flag is false skips upload. + * Covers: ctx->flag == false → skip upload, return 0. + */ +TEST_F(OndemandStrategyAccessorTest, Upload_FlagFalse_SkipsUpload) { + ctx.flag = false; + int result = fnOndemandUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_upload_archive_call_count, 0); +} + +/** + * @test ondemand_upload via accessor: upload failure propagates. + * Covers: upload_archive returns non-zero. + */ +TEST_F(OndemandStrategyAccessorTest, Upload_UploadFails_ReturnsError) { + EXPECT_CALL(mock_ops, upload_archive(&ctx, &session, _)) + .WillOnce(Return(-1)); + + int result = fnOndemandUpload(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test ondemand_cleanup via accessor: tar file exists and temp dir removed. + * Covers: file_exists → remove_file, dir_exists → remove_directory. + */ +TEST_F(OndemandStrategyAccessorTest, Cleanup_Success) { + InSequence seq; + EXPECT_CALL(mock_ops, file_exists(_)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, remove_file(_)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, remove_directory(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + + int result = fnOndemandCleanup(&ctx, &session, true); + EXPECT_EQ(result, 0); +} + +/** + * @test ondemand_cleanup via accessor: remove_directory fails returns -1. + * Covers: remove_directory returns false. + */ +TEST_F(OndemandStrategyAccessorTest, Cleanup_RemoveDirFails_ReturnsError) { + EXPECT_CALL(mock_ops, file_exists(_)) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, remove_directory(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(false)); + + int result = fnOndemandCleanup(&ctx, &session, true); + EXPECT_EQ(result, -1); +} + +// ---- Reboot strategy phase accessor tests ---- + +/** + * Test fixture for Reboot strategy static functions accessed via GTEST_ENABLE accessors. + */ +class RebootStrategyAccessorTest : public ::testing::Test { +protected: + void SetUp() override { + g_mock_file_ops = nullptr; + g_mock_dir_exists = true; + g_mock_add_timestamp_result = 0; + g_mock_collect_pcap_result = 0; + g_mock_create_archive_result = 0; + g_mock_upload_archive_result = 0; + g_mock_clear_packet_captures_result = 0; + g_mock_remove_directory_result = true; + + g_add_timestamp_call_count = 0; + g_collect_pcap_call_count = 0; + g_create_archive_call_count = 0; + g_upload_archive_call_count = 0; + g_clear_packet_captures_call_count = 0; + g_remove_directory_call_count = 0; + g_sleep_call_count = 0; + + memset(&ctx, 0, sizeof(ctx)); + strcpy(ctx.log_path, "/opt/logs"); + strcpy(ctx.prev_log_path, "/opt/PreviousLogs"); + strcpy(ctx.telemetry_path, "/tmp/telemetry"); + ctx.upload_on_reboot = 1; + + memset(&session, 0, sizeof(session)); + strcpy(session.archive_file, "reboot_logs.tar.gz"); + session.success = false; + + // Create sentinel files required by reboot_setup + CreateSentinel(BACKUP_LOGS_DONE_FLAG); + CreateSentinel(STT_FLAG); + CreateSentinel(PATH_FLAG_INVOCATION); + CreateSentinel(TELEMETRY_PREVLOGS_DONE_FLAG); + + fnRebootSetup = getRebootSetup(); + fnRebootArchive = getRebootArchive(); + fnRebootUpload = getRebootUpload(); + fnRebootCleanup = getRebootCleanup(); + } + + void TearDown() override { + g_mock_file_ops = nullptr; + unlink(BACKUP_LOGS_DONE_FLAG); + unlink(STT_FLAG); + unlink(PATH_FLAG_INVOCATION); + unlink(TELEMETRY_PREVLOGS_DONE_FLAG); + } + + void CreateSentinel(const char* path) { + int fd = open(path, O_CREAT | O_WRONLY, 0644); + if (fd >= 0) close(fd); + } + + RuntimeContext ctx; + SessionState session; + int (*fnRebootSetup)(RuntimeContext*, SessionState*); + int (*fnRebootArchive)(RuntimeContext*, SessionState*); + int (*fnRebootUpload)(RuntimeContext*, SessionState*); + int (*fnRebootCleanup)(RuntimeContext*, SessionState*, bool); +}; + +/** + * @test reboot_setup via accessor: BACKUP_LOGS_DONE_FLAG absent returns -1. + * Covers: stat(BACKUP_LOGS_DONE_FLAG) fails → early abort. + */ +TEST_F(RebootStrategyAccessorTest, Setup_BackupLogsDoneAbsent_ReturnsError) { + unlink(BACKUP_LOGS_DONE_FLAG); + int result = fnRebootSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test reboot_setup via accessor: PREV_LOG_PATH does not exist returns -1. + * Covers: dir_exists(ctx->prev_log_path) returns false. + */ +TEST_F(RebootStrategyAccessorTest, Setup_PrevLogPathNotExists_ReturnsError) { + g_mock_dir_exists = false; + int result = fnRebootSetup(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test reboot_archive via accessor: create_archive failure returns -1. + * Covers: create_archive returns non-zero. + */ +TEST_F(RebootStrategyAccessorTest, Archive_CreateArchiveFails_ReturnsError) { + g_mock_create_archive_result = -1; + int result = fnRebootArchive(&ctx, &session); + EXPECT_EQ(result, -1); +} + +/** + * @test reboot_archive via accessor: PCAP collected when include_pcap is true. + * Covers: include_pcap == true → collect_pcap_logs called. + */ +TEST_F(RebootStrategyAccessorTest, Archive_WithPcap_CollectsCalled) { + ctx.include_pcap = true; + int result = fnRebootArchive(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_collect_pcap_call_count, 1); +} + +/** + * @test reboot_archive via accessor: PCAP not collected when include_pcap is false. + * Covers: include_pcap == false → collect_pcap_logs skipped. + */ +TEST_F(RebootStrategyAccessorTest, Archive_NoPcap_SkipsCollect) { + ctx.include_pcap = false; + int result = fnRebootArchive(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_collect_pcap_call_count, 0); +} + +/** + * @test reboot_upload via accessor: Non-DCM mode always uploads. + * Covers: dcm_flag == 0 → should_upload = true regardless of upload_on_reboot. + */ +TEST_F(RebootStrategyAccessorTest, Upload_NonDcmMode_AlwaysUploads) { + ctx.dcm_flag = 0; + ctx.upload_on_reboot = 0; // Even with this off, Non-DCM always uploads + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_upload_archive_call_count, 1); + EXPECT_TRUE(session.success); +} + +/** + * @test reboot_upload via accessor: upload failure propagates. + * Covers: upload_archive returns non-zero in reboot strategy. + */ +TEST_F(RebootStrategyAccessorTest, Upload_UploadFails_ReturnsError) { + ctx.dcm_flag = 0; + g_mock_upload_archive_result = -1; + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, -1); + EXPECT_FALSE(session.success); +} + +/** + * @test reboot_upload via accessor: PCAP clearing triggered when include_pcap is true. + * Covers: include_pcap == true → clear_old_packet_captures called. + */ +TEST_F(RebootStrategyAccessorTest, Upload_WithPcap_ClearsCalled) { + ctx.dcm_flag = 0; + ctx.include_pcap = true; + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_clear_packet_captures_call_count, 1); +} + +/** + * @test reboot_upload via accessor: DCM mode with upload_on_reboot=1 uploads. + * Covers: dcm_flag == 1 && upload_on_reboot == 1 → should_upload = true. + */ +TEST_F(RebootStrategyAccessorTest, Upload_DcmMode_UploadOnRebootEnabled) { + ctx.dcm_flag = 1; + ctx.upload_on_reboot = 1; + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_upload_archive_call_count, 1); +} + +/** + * @test reboot_upload via accessor: DCM mode with upload_on_reboot=0 and unscheduled reboot. + * Covers: dcm_flag==1, upload_on_reboot==0, reboot_reason file missing → unscheduled → uploads. + */ +TEST_F(RebootStrategyAccessorTest, Upload_DcmMode_UnscheduledReboot_Uploads) { + ctx.dcm_flag = 1; + ctx.upload_on_reboot = 0; + // No reboot reason file → is_scheduled_reboot=false, RFC disable=false → should_upload=true + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_upload_archive_call_count, 1); +} + +/** + * @test reboot_upload via accessor: DRI logs uploaded when include_dri and path exists. + * Covers: include_dri==true && dir_exists(dri_log_path) → create_dri_archive + upload. + */ +TEST_F(RebootStrategyAccessorTest, Upload_DriLogs_Uploaded) { + ctx.dcm_flag = 0; + ctx.include_dri = true; + strcpy(ctx.dri_log_path, "/opt/logs/dri"); + g_mock_dir_exists = true; + + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, 0); + // Main upload + DRI upload + EXPECT_GE(g_upload_archive_call_count, 1); +} + +/** + * @test reboot_upload via accessor: PCAP not cleared when include_pcap is false. + * Covers: include_pcap == false → clear_old_packet_captures skipped. + */ +TEST_F(RebootStrategyAccessorTest, Upload_NoPcap_SkipsClear) { + ctx.dcm_flag = 0; + ctx.include_pcap = false; + int result = fnRebootUpload(&ctx, &session); + EXPECT_EQ(result, 0); + EXPECT_EQ(g_clear_packet_captures_call_count, 0); +} + +/** + * @test reboot_cleanup via accessor: successful cleanup creates backup and cleans prev_log_path. + * Covers: remove_timestamp, create_directory(perm_log_path), move_directory_contents, clean_directory. + */ +TEST_F(RebootStrategyAccessorTest, Cleanup_Success) { + // Run setup first to populate perm_log_path_storage + g_mock_dir_exists = true; + fnRebootSetup(&ctx, &session); + + int result = fnRebootCleanup(&ctx, &session, true); + EXPECT_EQ(result, 0); +} + +/** + * @test reboot_cleanup via accessor: dcm_flag=1, upload_on_reboot=0 writes to dcm_upload list. + * Covers: ctx->dcm_flag==1 && ctx->upload_on_reboot==0 → fopen dcm_upload_list. + */ +TEST_F(RebootStrategyAccessorTest, Cleanup_DcmMode_NoUpload_WritesToDcmList) { + ctx.dcm_flag = 1; + ctx.upload_on_reboot = 0; + + // Run setup first to populate perm_log_path_storage + g_mock_dir_exists = true; + fnRebootSetup(&ctx, &session); + + int result = fnRebootCleanup(&ctx, &session, false); + EXPECT_EQ(result, 0); +} + +/** + * @test dcm_setup via accessor: read_dcm_upload_flag returns false → returns -1. + * Covers: DCMSettings.conf has upload=false → early return -1. + */ +TEST_F(DcmStrategyAccessorTest, Setup_UploadFlagFalse_ReturnsError) { + // Create a DCMSettings.conf with upload=false + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + + EXPECT_CALL(mock_ops, dir_exists(StrEq("/tmp/dcm_logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, remove_directory(StrEq("/tmp/dcm_logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, create_directory(StrEq("/tmp/dcm_logs"))) + .WillOnce(Return(true)); + + // fopen for DCMSettings.conf returns a FILE* with upload=false content + char temp_file[64]; + snprintf(temp_file, sizeof(temp_file), "/tmp/dcm_settings_test_%d", getpid()); + int fd = open(temp_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + const char* content = "urn:settings:LogUploadSettings:upload=false\n"; + write(fd, content, strlen(content)); + close(fd); + fd = open(temp_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + + EXPECT_CALL(mock_ops, fopen(_, _)).WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)).WillOnce(Return(0)); + + int result = fnDcmSetup(&ctx, &session); + EXPECT_EQ(result, -1); + + g_mock_file_ops = nullptr; + fclose(real_fp); + unlink(temp_file); +} + +/** + * @test ondemand_setup via accessor: temp directory already exists gets cleaned. + * Covers: dir_exists(ONDEMAND_TEMP_DIR)==true → remove_directory called before create. + */ +TEST_F(OndemandStrategyAccessorTest, Setup_TempDirExists_CleansFirst) { + InSequence seq; + EXPECT_CALL(mock_ops, dir_exists(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, has_log_files(StrEq("/opt/logs"))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, remove_directory(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, create_directory(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, collect_logs(&ctx, &session, StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(3)); + EXPECT_CALL(mock_ops, fopen(_, StrEq("a"))) + .WillOnce(Return(reinterpret_cast(0x123))); + EXPECT_CALL(mock_ops, fprintf(_, _, _)) + .WillOnce(Return(10)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + EXPECT_CALL(mock_ops, file_exists(_)) + .WillOnce(Return(false)); + + int result = fnOndemandSetup(&ctx, &session); + EXPECT_EQ(0, result); +} + +/** + * @test ondemand_cleanup via accessor: temp dir does not exist → success without removal. + * Covers: dir_exists(ONDEMAND_TEMP_DIR) returns false → skip removal. + */ +TEST_F(OndemandStrategyAccessorTest, Cleanup_TempDirNotExists_Succeeds) { + EXPECT_CALL(mock_ops, file_exists(_)) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(ONDEMAND_TEMP_DIR))) + .WillOnce(Return(false)); + + int result = fnOndemandCleanup(&ctx, &session, true); + EXPECT_EQ(result, 0); +} + +/** + * @test ondemand_upload via accessor: upload success returns 0. + * Covers: upload_archive returns 0 → return 0. + */ +TEST_F(OndemandStrategyAccessorTest, Upload_Success) { + EXPECT_CALL(mock_ops, upload_archive(&ctx, &session, _)) + .WillOnce(Return(0)); + + int result = fnOndemandUpload(&ctx, &session); + EXPECT_EQ(result, 0); +} + +// ---- File copy / DCM upload list static function tests ---- + +class FileCopyTest : public ::testing::Test { +protected: + void SetUp() override { + g_mock_file_ops = nullptr; + g_mock_dir_exists = true; + g_mock_create_directory_result = true; + g_copy_file_should_fail = false; + + snprintf(src_dir_, sizeof(src_dir_), "/tmp/strat_src_%d", getpid()); + snprintf(dest_dir_, sizeof(dest_dir_), "/tmp/strat_dst_%d", getpid()); + mkdir(src_dir_, 0755); + mkdir(dest_dir_, 0755); + + fnCopyOptLogsFiles = getCopyOptLogsFiles(); + fnCopyDirRecursive = getCopyDirRecursive(); + fnCopyAllFilesToDcm = getCopyAllFilesToDcm(); + fnProcessDcmUploadList = getProcessDcmUploadList(); + } + + void TearDown() override { + g_mock_file_ops = nullptr; + g_copy_file_should_fail = false; + g_mock_create_directory_result = true; + RemoveTree(src_dir_); + RemoveTree(dest_dir_); + } + + void CreateFileIn(const char* dir, const char* name) { + char path[512]; + snprintf(path, sizeof(path), "%s/%s", dir, name); + int fd = open(path, O_CREAT | O_WRONLY, 0644); + if (fd >= 0) { write(fd, "data", 4); close(fd); } + } + + void MakeSubdir(const char* parent, const char* name, char* out, size_t out_sz) { + snprintf(out, out_sz, "%s/%s", parent, name); + mkdir(out, 0755); + } + + void RemoveTree(const char* path) { + DIR* d = opendir(path); + if (!d) return; + struct dirent* e; + char child[512]; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) + continue; + snprintf(child, sizeof(child), "%s/%s", path, e->d_name); + if (e->d_type == DT_DIR) { + RemoveTree(child); + } else { + unlink(child); + } + } + closedir(d); + rmdir(path); + } + + char src_dir_[256]; + char dest_dir_[256]; + int (*fnCopyOptLogsFiles)(const char*, const char*); + int (*fnCopyDirRecursive)(const char*, const char*); + int (*fnCopyAllFilesToDcm)(const char*, const char*); + int (*fnProcessDcmUploadList)(RuntimeContext*); +}; + +// ---- copy_opt_logs_files tests ---- + +/** + * @test copy_opt_logs_files: nonexistent source directory returns -1. + * Covers: opendir fails path. + */ +TEST_F(FileCopyTest, CopyOptLogs_NonexistentSrc_ReturnsError) { + int result = fnCopyOptLogsFiles("/nonexistent_dir_xyz", dest_dir_); + EXPECT_EQ(result, -1); +} + +/** + * @test copy_opt_logs_files: empty directory returns 0. + * Covers: readdir loop with no entries. + */ +TEST_F(FileCopyTest, CopyOptLogs_EmptyDir_ReturnsZero) { + int result = fnCopyOptLogsFiles(src_dir_, dest_dir_); + EXPECT_EQ(result, 0); +} + +/** + * @test copy_opt_logs_files: copies regular files and returns count. + * Covers: d_type != DT_DIR → copy_file called. + */ +TEST_F(FileCopyTest, CopyOptLogs_CopiesRegularFiles) { + CreateFileIn(src_dir_, "test1.log"); + CreateFileIn(src_dir_, "test2.txt"); + int result = fnCopyOptLogsFiles(src_dir_, dest_dir_); + EXPECT_EQ(result, 2); +} + +/** + * @test copy_opt_logs_files: subdirectories are skipped. + * Covers: d_type == DT_DIR continue path. + */ +TEST_F(FileCopyTest, CopyOptLogs_SkipsSubdirectories) { + CreateFileIn(src_dir_, "test.log"); + char sub[512]; + MakeSubdir(src_dir_, "subdir", sub, sizeof(sub)); + int result = fnCopyOptLogsFiles(src_dir_, dest_dir_); + EXPECT_EQ(result, 1); +} + +/** + * @test copy_opt_logs_files: failed copy_file is not counted. + * Covers: copy_file returns false → count not incremented. + */ +TEST_F(FileCopyTest, CopyOptLogs_CopyFails_NotCounted) { + CreateFileIn(src_dir_, "test.log"); + g_copy_file_should_fail = true; + int result = fnCopyOptLogsFiles(src_dir_, dest_dir_); + EXPECT_EQ(result, 0); +} + +// ---- copy_dir_recursive tests ---- + +/** + * @test copy_dir_recursive: nonexistent source returns -1. + * Covers: opendir fails. + */ +TEST_F(FileCopyTest, CopyDirRecursive_NonexistentSrc_ReturnsError) { + int result = fnCopyDirRecursive("/nonexistent_dir_xyz", dest_dir_); + EXPECT_EQ(result, -1); +} + +/** + * @test copy_dir_recursive: empty directory returns 0. + * Covers: readdir loop with no entries. + */ +TEST_F(FileCopyTest, CopyDirRecursive_EmptyDir_ReturnsZero) { + int result = fnCopyDirRecursive(src_dir_, dest_dir_); + EXPECT_EQ(result, 0); +} + +/** + * @test copy_dir_recursive: flat files are copied and counted. + * Covers: d_type != DT_DIR → copy_file. + */ +TEST_F(FileCopyTest, CopyDirRecursive_FilesOnly_ReturnsCount) { + CreateFileIn(src_dir_, "file1.log"); + CreateFileIn(src_dir_, "file2.log"); + int result = fnCopyDirRecursive(src_dir_, dest_dir_); + EXPECT_EQ(result, 2); +} + +/** + * @test copy_dir_recursive: recurses into subdirectories. + * Covers: d_type == DT_DIR → create_directory + recursive call. + */ +TEST_F(FileCopyTest, CopyDirRecursive_WithSubdirs_Recurses) { + CreateFileIn(src_dir_, "top.log"); + char sub[512]; + MakeSubdir(src_dir_, "child", sub, sizeof(sub)); + CreateFileIn(sub, "nested.log"); + int result = fnCopyDirRecursive(src_dir_, dest_dir_); + EXPECT_EQ(result, 2); +} + +/** + * @test copy_dir_recursive: create_directory failure skips subdirectory. + * Covers: create_directory returns false → continue. + */ +TEST_F(FileCopyTest, CopyDirRecursive_CreateDirFails_SkipsSubdir) { + CreateFileIn(src_dir_, "top.log"); + char sub[512]; + MakeSubdir(src_dir_, "child", sub, sizeof(sub)); + CreateFileIn(sub, "nested.log"); + g_mock_create_directory_result = false; + int result = fnCopyDirRecursive(src_dir_, dest_dir_); + EXPECT_EQ(result, 1); +} + +/** + * @test copy_dir_recursive: copy_file failure in subdir not counted. + * Covers: copy_file returns false in recursive call. + */ +TEST_F(FileCopyTest, CopyDirRecursive_CopyFails_NotCounted) { + CreateFileIn(src_dir_, "top.log"); + g_copy_file_should_fail = true; + int result = fnCopyDirRecursive(src_dir_, dest_dir_); + EXPECT_EQ(result, 0); +} + +// ---- copy_all_files_to_dcm tests ---- + +/** + * @test copy_all_files_to_dcm: nonexistent source returns -1. + * Covers: opendir fails. + */ +TEST_F(FileCopyTest, CopyAllToDcm_NonexistentSrc_ReturnsError) { + int result = fnCopyAllFilesToDcm("/nonexistent_dir_xyz", dest_dir_); + EXPECT_EQ(result, -1); +} + +/** + * @test copy_all_files_to_dcm: "dcm" directory is excluded. + * Covers: exclude[] match for "dcm". + */ +TEST_F(FileCopyTest, CopyAllToDcm_ExcludesDcm) { + CreateFileIn(src_dir_, "regular.log"); + char sub[512]; + MakeSubdir(src_dir_, "dcm", sub, sizeof(sub)); + CreateFileIn(sub, "should_skip.log"); + int result = fnCopyAllFilesToDcm(src_dir_, dest_dir_); + EXPECT_EQ(result, 1); +} + +/** + * @test copy_all_files_to_dcm: "PreviousLogs" directory is excluded. + * Covers: exclude[] match for "PreviousLogs". + */ +TEST_F(FileCopyTest, CopyAllToDcm_ExcludesPreviousLogs) { + CreateFileIn(src_dir_, "regular.log"); + char sub[512]; + MakeSubdir(src_dir_, "PreviousLogs", sub, sizeof(sub)); + CreateFileIn(sub, "should_skip.log"); + int result = fnCopyAllFilesToDcm(src_dir_, dest_dir_); + EXPECT_EQ(result, 1); +} + +/** + * @test copy_all_files_to_dcm: "PreviousLogs_backup" directory is excluded. + * Covers: exclude[] match for "PreviousLogs_backup". + */ +TEST_F(FileCopyTest, CopyAllToDcm_ExcludesPreviousLogsBackup) { + CreateFileIn(src_dir_, "regular.log"); + char sub[512]; + MakeSubdir(src_dir_, "PreviousLogs_backup", sub, sizeof(sub)); + CreateFileIn(sub, "should_skip.log"); + int result = fnCopyAllFilesToDcm(src_dir_, dest_dir_); + EXPECT_EQ(result, 1); +} + +/** + * @test copy_all_files_to_dcm: non-excluded subdirectory is copied recursively. + * Covers: d_type == DT_DIR without exclude match → create_directory + copy_dir_recursive. + */ +TEST_F(FileCopyTest, CopyAllToDcm_CopiesNonExcludedSubdirs) { + char sub[512]; + MakeSubdir(src_dir_, "other_logs", sub, sizeof(sub)); + CreateFileIn(sub, "file.log"); + int result = fnCopyAllFilesToDcm(src_dir_, dest_dir_); + EXPECT_EQ(result, 1); +} + +/** + * @test copy_all_files_to_dcm: empty directory with only excluded entries returns 0. + * Covers: all entries skipped by exclude list. + */ +TEST_F(FileCopyTest, CopyAllToDcm_OnlyExcluded_ReturnsZero) { + char sub[512]; + MakeSubdir(src_dir_, "dcm", sub, sizeof(sub)); + MakeSubdir(src_dir_, "PreviousLogs", sub, sizeof(sub)); + MakeSubdir(src_dir_, "PreviousLogs_backup", sub, sizeof(sub)); + int result = fnCopyAllFilesToDcm(src_dir_, dest_dir_); + EXPECT_EQ(result, 0); +} + +// ---- process_dcm_upload_list tests ---- + +/** + * @test process_dcm_upload_list: no list file returns 0. + * Covers: fopen returns NULL → immediate return 0. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_NoFile_ReturnsZero) { + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strcpy(ctx.log_path, "/tmp/nonexistent_strat_xyz"); + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 0); +} + +/** + * @test process_dcm_upload_list: empty list file returns 0. + * Covers: fgets returns NULL on empty file → 0 entries processed. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_EmptyFile_ReturnsZero) { + char list_file[512]; + snprintf(list_file, sizeof(list_file), "%s/dcm_upload", src_dir_); + int fd = open(list_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + ASSERT_GE(fd, 0); + close(fd); + fd = open(list_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, StrEq("r"))) + .WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.log_path, src_dir_, sizeof(ctx.log_path) - 1); + strncpy(ctx.dcm_log_path, dest_dir_, sizeof(ctx.dcm_log_path) - 1); + + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 0); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test process_dcm_upload_list: entry with non-existent source dir is skipped. + * Covers: dir_exists(line) returns false → skip. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_DirNotExists_Skipped) { + char list_file[512]; + snprintf(list_file, sizeof(list_file), "%s/dcm_upload", src_dir_); + int fd = open(list_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + ASSERT_GE(fd, 0); + const char* content = "/tmp/no_such_batch_dir\n"; + write(fd, content, strlen(content)); + close(fd); + fd = open(list_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + ASSERT_NE(nullptr, real_fp); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, StrEq("r"))) + .WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, dir_exists(StrEq("/tmp/no_such_batch_dir"))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.log_path, src_dir_, sizeof(ctx.log_path) - 1); + strncpy(ctx.dcm_log_path, dest_dir_, sizeof(ctx.dcm_log_path) - 1); + + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 0); + + g_mock_file_ops = nullptr; + fclose(real_fp); +} + +/** + * @test process_dcm_upload_list: duplicate dest directory is skipped. + * Covers: dir_exists(dest_subdir) returns true → skip. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_DuplicateDest_Skipped) { + char batch_dir[256]; + snprintf(batch_dir, sizeof(batch_dir), "/tmp/strat_batch_%d", getpid()); + mkdir(batch_dir, 0755); + + char list_file[512]; + snprintf(list_file, sizeof(list_file), "%s/dcm_upload", src_dir_); + int fd = open(list_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + ASSERT_GE(fd, 0); + char content[512]; + snprintf(content, sizeof(content), "%s\n", batch_dir); + write(fd, content, strlen(content)); + close(fd); + fd = open(list_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + ASSERT_NE(nullptr, real_fp); + + char expected_dest[512]; + snprintf(expected_dest, sizeof(expected_dest), "%s/%s", + dest_dir_, strrchr(batch_dir, '/') + 1); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, StrEq("r"))) + .WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(batch_dir))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(expected_dest))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.log_path, src_dir_, sizeof(ctx.log_path) - 1); + strncpy(ctx.dcm_log_path, dest_dir_, sizeof(ctx.dcm_log_path) - 1); + + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 0); + + g_mock_file_ops = nullptr; + fclose(real_fp); + RemoveTree(batch_dir); +} + +/** + * @test process_dcm_upload_list: valid entry is processed and count returned. + * Covers: dir_exists(source) true, not duplicate, create_directory + copy_dir_recursive. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_ValidEntry_ProcessesAndReturns) { + char batch_dir[256]; + snprintf(batch_dir, sizeof(batch_dir), "/tmp/strat_batch_%d", getpid()); + mkdir(batch_dir, 0755); + CreateFileIn(batch_dir, "batch_file.log"); + + char list_file[512]; + snprintf(list_file, sizeof(list_file), "%s/dcm_upload", src_dir_); + int fd = open(list_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + ASSERT_GE(fd, 0); + char content[512]; + snprintf(content, sizeof(content), "%s\n", batch_dir); + write(fd, content, strlen(content)); + close(fd); + fd = open(list_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + ASSERT_NE(nullptr, real_fp); + + char expected_dest[512]; + snprintf(expected_dest, sizeof(expected_dest), "%s/%s", + dest_dir_, strrchr(batch_dir, '/') + 1); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, StrEq("r"))) + .WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(batch_dir))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(expected_dest))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, create_directory(StrEq(expected_dest))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.log_path, src_dir_, sizeof(ctx.log_path) - 1); + strncpy(ctx.dcm_log_path, dest_dir_, sizeof(ctx.dcm_log_path) - 1); + + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 1); + + g_mock_file_ops = nullptr; + fclose(real_fp); + RemoveTree(batch_dir); +} + +/** + * @test process_dcm_upload_list: create_directory failure skips entry. + * Covers: create_directory returns false → continue. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_CreateDirFails_Skipped) { + char batch_dir[256]; + snprintf(batch_dir, sizeof(batch_dir), "/tmp/strat_batch_%d", getpid()); + mkdir(batch_dir, 0755); + + char list_file[512]; + snprintf(list_file, sizeof(list_file), "%s/dcm_upload", src_dir_); + int fd = open(list_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + ASSERT_GE(fd, 0); + char content[512]; + snprintf(content, sizeof(content), "%s\n", batch_dir); + write(fd, content, strlen(content)); + close(fd); + fd = open(list_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + ASSERT_NE(nullptr, real_fp); + + char expected_dest[512]; + snprintf(expected_dest, sizeof(expected_dest), "%s/%s", + dest_dir_, strrchr(batch_dir, '/') + 1); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, StrEq("r"))) + .WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(batch_dir))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(expected_dest))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, create_directory(StrEq(expected_dest))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.log_path, src_dir_, sizeof(ctx.log_path) - 1); + strncpy(ctx.dcm_log_path, dest_dir_, sizeof(ctx.dcm_log_path) - 1); + + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 0); + + g_mock_file_ops = nullptr; + fclose(real_fp); + RemoveTree(batch_dir); +} + +/** + * @test process_dcm_upload_list: blank lines in list are skipped. + * Covers: strlen(line) == 0 after trim → continue. + */ +TEST_F(FileCopyTest, ProcessDcmUploadList_BlankLines_Skipped) { + char batch_dir[256]; + snprintf(batch_dir, sizeof(batch_dir), "/tmp/strat_batch_%d", getpid()); + mkdir(batch_dir, 0755); + CreateFileIn(batch_dir, "file.log"); + + char list_file[512]; + snprintf(list_file, sizeof(list_file), "%s/dcm_upload", src_dir_); + int fd = open(list_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); + ASSERT_GE(fd, 0); + char content[512]; + snprintf(content, sizeof(content), "\n\n%s\n\n", batch_dir); + write(fd, content, strlen(content)); + close(fd); + fd = open(list_file, O_RDONLY); + FILE* real_fp = fdopen(fd, "r"); + ASSERT_NE(nullptr, real_fp); + + char expected_dest[512]; + snprintf(expected_dest, sizeof(expected_dest), "%s/%s", + dest_dir_, strrchr(batch_dir, '/') + 1); + + MockFileOperations mock_ops; + g_mock_file_ops = &mock_ops; + EXPECT_CALL(mock_ops, fopen(_, StrEq("r"))) + .WillOnce(Return(real_fp)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(batch_dir))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, dir_exists(StrEq(expected_dest))) + .WillOnce(Return(false)); + EXPECT_CALL(mock_ops, create_directory(StrEq(expected_dest))) + .WillOnce(Return(true)); + EXPECT_CALL(mock_ops, fclose(_)) + .WillOnce(Return(0)); + + RuntimeContext ctx; + memset(&ctx, 0, sizeof(ctx)); + strncpy(ctx.log_path, src_dir_, sizeof(ctx.log_path) - 1); + strncpy(ctx.dcm_log_path, dest_dir_, sizeof(ctx.dcm_log_path) - 1); + + int result = fnProcessDcmUploadList(&ctx); + EXPECT_EQ(result, 1); + + g_mock_file_ops = nullptr; + fclose(real_fp); + RemoveTree(batch_dir); +} + // Entry point for the test executable int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/uploadstblogs/unittest/upload_engine_gtest.cpp b/uploadstblogs/unittest/upload_engine_gtest.cpp index 2c9dc58c..eaab1efe 100755 --- a/uploadstblogs/unittest/upload_engine_gtest.cpp +++ b/uploadstblogs/unittest/upload_engine_gtest.cpp @@ -426,6 +426,80 @@ TEST_F(UploadEngineTest, FullWorkflow_FallbackSuccess) { EXPECT_FALSE(g_emit_success_called); } +// ==================== single_attempt_upload TESTS (static accessor) ==================== + + + UploadResult (*getSingleAttemptUpload(void))(RuntimeContext*, SessionState*, UploadPath); + + +class SingleAttemptUploadTest : public ::testing::Test { +protected: + void SetUp() override { + g_execute_direct_called = false; + g_execute_codebig_called = false; + g_mock_path_result = UPLOADSTB_SUCCESS; + + memset(&ctx, 0, sizeof(ctx)); + memset(&session, 0, sizeof(session)); + session.primary = PATH_DIRECT; + session.fallback = PATH_CODEBIG; + strcpy(session.archive_file, "/tmp/test.tar.gz"); + + fnSingleAttemptUpload = getSingleAttemptUpload(); + ASSERT_NE(nullptr, fnSingleAttemptUpload); + } + RuntimeContext ctx; + SessionState session; + UploadResult (*fnSingleAttemptUpload)(RuntimeContext*, SessionState*, UploadPath); +}; + +TEST_F(SingleAttemptUploadTest, NullContext_ReturnsFailed) { + UploadResult result = fnSingleAttemptUpload(nullptr, &session, PATH_DIRECT); + EXPECT_EQ(UPLOADSTB_FAILED, result); +} + +TEST_F(SingleAttemptUploadTest, NullSession_ReturnsFailed) { + UploadResult result = fnSingleAttemptUpload(&ctx, nullptr, PATH_DIRECT); + EXPECT_EQ(UPLOADSTB_FAILED, result); +} + +TEST_F(SingleAttemptUploadTest, DirectPath_CallsExecuteDirect) { + g_mock_path_result = UPLOADSTB_SUCCESS; + UploadResult result = fnSingleAttemptUpload(&ctx, &session, PATH_DIRECT); + EXPECT_EQ(UPLOADSTB_SUCCESS, result); + EXPECT_TRUE(g_execute_direct_called); + EXPECT_FALSE(g_execute_codebig_called); +} + +TEST_F(SingleAttemptUploadTest, CodebigPath_CallsExecuteCodebig) { + g_mock_path_result = UPLOADSTB_SUCCESS; + UploadResult result = fnSingleAttemptUpload(&ctx, &session, PATH_CODEBIG); + EXPECT_EQ(UPLOADSTB_SUCCESS, result); + EXPECT_FALSE(g_execute_direct_called); + EXPECT_TRUE(g_execute_codebig_called); +} + +TEST_F(SingleAttemptUploadTest, PathNone_ReturnsFailed) { + UploadResult result = fnSingleAttemptUpload(&ctx, &session, PATH_NONE); + EXPECT_EQ(UPLOADSTB_FAILED, result); + EXPECT_FALSE(g_execute_direct_called); + EXPECT_FALSE(g_execute_codebig_called); +} + +TEST_F(SingleAttemptUploadTest, DirectPath_PropagatesFailure) { + g_mock_path_result = UPLOADSTB_FAILED; + UploadResult result = fnSingleAttemptUpload(&ctx, &session, PATH_DIRECT); + EXPECT_EQ(UPLOADSTB_FAILED, result); + EXPECT_TRUE(g_execute_direct_called); +} + +TEST_F(SingleAttemptUploadTest, CodebigPath_PropagatesRetry) { + g_mock_path_result = UPLOADSTB_RETRY; + UploadResult result = fnSingleAttemptUpload(&ctx, &session, PATH_CODEBIG); + EXPECT_EQ(UPLOADSTB_RETRY, result); + EXPECT_TRUE(g_execute_codebig_called); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/uploadstblogs/unittest/uploadlogsnow_gtest.cpp b/uploadstblogs/unittest/uploadlogsnow_gtest.cpp index 842f4946..d611ab0d 100644 --- a/uploadstblogs/unittest/uploadlogsnow_gtest.cpp +++ b/uploadstblogs/unittest/uploadlogsnow_gtest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // Mock RDK_LOG before including other headers #ifdef GTEST_ENABLE @@ -160,6 +161,7 @@ class UploadLogsNowTest : public ::testing::Test { // Create a temporary test directory test_log_dir = std::string("/tmp/uploadlogsnow_test_") + std::to_string(getpid()); + mkdir(test_log_dir.c_str(), 0755); // Initialize test context with safe paths memset(&ctx, 0, sizeof(ctx)); @@ -168,6 +170,22 @@ class UploadLogsNowTest : public ::testing::Test { ctx.uploadlogsnow_mode = true; } + void CreateTestLogFiles() { + for (int i = 0; i < 3; i++) { + std::string path = test_log_dir + "/test" + std::to_string(i) + ".log"; + int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd >= 0) { + FILE* fp = fdopen(fd, "w"); + if (fp) { + fprintf(fp, "log data %d", i); + fclose(fp); + } else { + close(fd); + } + } + } + } + void TearDown() override { // Clean up test directory if it was created if (!test_log_dir.empty()) { @@ -196,65 +214,235 @@ TEST_F(UploadLogsNowTest, ExecuteWorkflow_CreateDirectoryFails) { } TEST_F(UploadLogsNowTest, ExecuteWorkflow_CopyFilesFails) { + CreateTestLogFiles(); g_copy_file_should_fail = true; + // When all copy_file calls fail, copied_count=0 → treated as "no files to upload" int result = execute_uploadlogsnow_workflow(&ctx); - EXPECT_EQ(-1, result); // Should fail due to file copy failure + EXPECT_EQ(0, result); } TEST_F(UploadLogsNowTest, ExecuteWorkflow_CreateArchiveFails) { + CreateTestLogFiles(); g_create_archive_should_fail = true; - g_copy_files_return_count = 3; // Some files copied int result = execute_uploadlogsnow_workflow(&ctx); - EXPECT_EQ(-1, result); // Should fail due to archive creation failure + EXPECT_EQ(-1, result); } TEST_F(UploadLogsNowTest, ExecuteWorkflow_ArchiveFileNotFound) { + CreateTestLogFiles(); g_file_exists_return_value = false; - g_copy_files_return_count = 3; // Some files copied int result = execute_uploadlogsnow_workflow(&ctx); - EXPECT_EQ(-1, result); // Should fail when archive file doesn't exist after creation + EXPECT_EQ(-1, result); } TEST_F(UploadLogsNowTest, ExecuteWorkflow_UploadFails) { + CreateTestLogFiles(); g_execute_upload_cycle_return_value = false; - g_copy_files_return_count = 3; // Some files copied int result = execute_uploadlogsnow_workflow(&ctx); - EXPECT_EQ(-1, result); // Should fail when upload fails + EXPECT_EQ(-1, result); } TEST_F(UploadLogsNowTest, IntegrationTest_CascadingFailures) { - // Test various failure scenarios one by one - // First test: directory creation fails (early failure) g_create_directory_should_fail = true; int result = execute_uploadlogsnow_workflow(&ctx); EXPECT_EQ(-1, result); - // Reset and test copy failure - SetUp(); // Reset all mocks - g_copy_file_should_fail = true; - result = execute_uploadlogsnow_workflow(&ctx); - EXPECT_EQ(-1, result); - // Reset and test archive creation failure - SetUp(); // Reset all mocks + SetUp(); + CreateTestLogFiles(); g_create_archive_should_fail = true; - g_copy_files_return_count = 3; // Some files copied result = execute_uploadlogsnow_workflow(&ctx); EXPECT_EQ(-1, result); // Reset and test upload failure - SetUp(); // Reset all mocks + SetUp(); + CreateTestLogFiles(); g_execute_upload_cycle_return_value = false; - g_copy_files_return_count = 3; // Some files copied result = execute_uploadlogsnow_workflow(&ctx); EXPECT_EQ(-1, result); } +TEST_F(UploadLogsNowTest, ExecuteWorkflow_NoFilesToUpload) { + // Empty directory - no files to copy + int result = execute_uploadlogsnow_workflow(&ctx); + EXPECT_EQ(0, result); +} + +TEST_F(UploadLogsNowTest, ExecuteWorkflow_UsesDcmLogPathFromContext) { + strncpy(ctx.dcm_log_path, "/tmp/custom_dcm", sizeof(ctx.dcm_log_path) - 1); + CreateTestLogFiles(); + int result = execute_uploadlogsnow_workflow(&ctx); + EXPECT_EQ(0, result); +} + +TEST_F(UploadLogsNowTest, ExecuteWorkflow_TimestampFailureNonFatal) { + g_add_timestamp_should_fail = true; + CreateTestLogFiles(); + int result = execute_uploadlogsnow_workflow(&ctx); + EXPECT_EQ(0, result); +} + +TEST_F(UploadLogsNowTest, ExecuteWorkflow_CleanupFailureNonFatal) { + g_remove_directory_should_fail = true; + CreateTestLogFiles(); + int result = execute_uploadlogsnow_workflow(&ctx); + EXPECT_EQ(0, result); +} + +// ==================== STATIC FUNCTION ACCESSOR TESTS ==================== + +extern "C" { + int (*getWriteUploadStatus(void))(const char*); + int (*getShouldExcludeFile(void))(const char*); + int (*getCopyFilesToDcmPath(void))(const char*, const char*); +} + +// ---- should_exclude_file tests ---- + +class ShouldExcludeFileTest : public ::testing::Test { +protected: + void SetUp() override { + fnShouldExcludeFile = getShouldExcludeFile(); + ASSERT_NE(nullptr, fnShouldExcludeFile); + } + int (*fnShouldExcludeFile)(const char*); +}; + +TEST_F(ShouldExcludeFileTest, ExcludesDcm) { + EXPECT_EQ(1, fnShouldExcludeFile("dcm")); +} + +TEST_F(ShouldExcludeFileTest, ExcludesPreviousLogsBackup) { + EXPECT_EQ(1, fnShouldExcludeFile("PreviousLogs_backup")); +} + +TEST_F(ShouldExcludeFileTest, ExcludesPreviousLogs) { + EXPECT_EQ(1, fnShouldExcludeFile("PreviousLogs")); +} + +TEST_F(ShouldExcludeFileTest, DoesNotExcludeRegularFile) { + EXPECT_EQ(0, fnShouldExcludeFile("messages.log")); +} + +TEST_F(ShouldExcludeFileTest, DoesNotExcludePartialMatch) { + EXPECT_EQ(0, fnShouldExcludeFile("dcm_settings.conf")); +} + +TEST_F(ShouldExcludeFileTest, DoesNotExcludeEmptyString) { + EXPECT_EQ(0, fnShouldExcludeFile("")); +} + +// ---- write_upload_status tests ---- + +class WriteUploadStatusTest : public ::testing::Test { +protected: + void SetUp() override { + fnWriteUploadStatus = getWriteUploadStatus(); + ASSERT_NE(nullptr, fnWriteUploadStatus); + } + void TearDown() override { + unlink(STATUS_FILE); + } + int (*fnWriteUploadStatus)(const char*); +}; + +TEST_F(WriteUploadStatusTest, WritesTriggeredStatus) { + int result = fnWriteUploadStatus("Triggered"); + EXPECT_EQ(0, result); + // Verify file was written + FILE* fp = fopen(STATUS_FILE, "r"); + if (fp) { + char buf[256] = {0}; + fgets(buf, sizeof(buf), fp); + fclose(fp); + EXPECT_TRUE(strstr(buf, "Triggered") != nullptr); + } +} + +TEST_F(WriteUploadStatusTest, WritesCompleteStatus) { + int result = fnWriteUploadStatus("Complete"); + EXPECT_EQ(0, result); +} + +TEST_F(WriteUploadStatusTest, WritesFailedStatus) { + int result = fnWriteUploadStatus("Failed"); + EXPECT_EQ(0, result); +} + +// ---- copy_files_to_dcm_path tests ---- + +class CopyFilesToDcmPathTest : public ::testing::Test { +protected: + void SetUp() override { + g_copy_file_should_fail = false; + fnCopyFilesToDcmPath = getCopyFilesToDcmPath(); + ASSERT_NE(nullptr, fnCopyFilesToDcmPath); + + src_dir = std::string("/tmp/copy_src_") + std::to_string(getpid()); + dest_dir = std::string("/tmp/copy_dst_") + std::to_string(getpid()); + mkdir(src_dir.c_str(), 0755); + mkdir(dest_dir.c_str(), 0755); + } + void TearDown() override { + std::string cmd = "rm -rf " + src_dir + " " + dest_dir; + system(cmd.c_str()); + } + void CreateTestFile(const std::string& dir, const char* name) { + std::string path = dir + "/" + name; + FILE* fp = fopen(path.c_str(), "w"); + if (fp) { fprintf(fp, "test"); fclose(fp); } + } + int (*fnCopyFilesToDcmPath)(const char*, const char*); + std::string src_dir; + std::string dest_dir; +}; + +TEST_F(CopyFilesToDcmPathTest, EmptyDirectory_ReturnsZero) { + int result = fnCopyFilesToDcmPath(src_dir.c_str(), dest_dir.c_str()); + EXPECT_EQ(0, result); +} + +TEST_F(CopyFilesToDcmPathTest, InvalidSrcDir_ReturnsError) { + int result = fnCopyFilesToDcmPath("/nonexistent_path_xyz", dest_dir.c_str()); + EXPECT_EQ(-1, result); +} + +TEST_F(CopyFilesToDcmPathTest, CopiesRegularFiles) { + CreateTestFile(src_dir, "test.log"); + CreateTestFile(src_dir, "other.txt"); + int result = fnCopyFilesToDcmPath(src_dir.c_str(), dest_dir.c_str()); + EXPECT_EQ(2, result); +} + +TEST_F(CopyFilesToDcmPathTest, ExcludesDcmDirectory) { + CreateTestFile(src_dir, "test.log"); + mkdir((src_dir + "/dcm").c_str(), 0755); + // dcm is excluded; only test.log should be copied + int result = fnCopyFilesToDcmPath(src_dir.c_str(), dest_dir.c_str()); + EXPECT_EQ(1, result); +} + +TEST_F(CopyFilesToDcmPathTest, ExcludesPreviousLogs) { + CreateTestFile(src_dir, "test.log"); + mkdir((src_dir + "/PreviousLogs").c_str(), 0755); + mkdir((src_dir + "/PreviousLogs_backup").c_str(), 0755); + int result = fnCopyFilesToDcmPath(src_dir.c_str(), dest_dir.c_str()); + EXPECT_EQ(1, result); +} + +TEST_F(CopyFilesToDcmPathTest, CopyFailure_CountsSuccessful) { + CreateTestFile(src_dir, "good.log"); + CreateTestFile(src_dir, "also_good.txt"); + g_copy_file_should_fail = true; + int result = fnCopyFilesToDcmPath(src_dir.c_str(), dest_dir.c_str()); + EXPECT_EQ(0, result); +} + } // namespace int main(int argc, char **argv) {