diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cfa3b6..ae499b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,8 +213,12 @@ if (UA2F_BUILD_TESTS) ua2f_test test/util_test.cc test/cache_test.cc + test/statistics_test.cc + test/cli_test.cc src/util.c src/cache.c + src/statistics.c + src/cli.c ) target_link_libraries( ua2f_test diff --git a/test/cache_test.cc b/test/cache_test.cc index 9789423..deb29b4 100644 --- a/test/cache_test.cc +++ b/test/cache_test.cc @@ -46,4 +46,73 @@ TEST_F(CacheTest, CacheDoesNotContainNonexistentEntry) { addr_port nonexistent_addr{}; nonexistent_addr.addr.ip4 = 54321; EXPECT_FALSE(cache_contains(nonexistent_addr)); +} + +TEST_F(CacheTest, MultipleDifferentAddresses) { + addr_port addr1{}, addr2{}, addr3{}; + addr1.addr.ip4 = 1001; + addr1.port = 80; + addr2.addr.ip4 = 1002; + addr2.port = 443; + addr3.addr.ip4 = 1003; + addr3.port = 8080; + + // Initially none should be in cache + EXPECT_FALSE(cache_contains(addr1)); + EXPECT_FALSE(cache_contains(addr2)); + EXPECT_FALSE(cache_contains(addr3)); + + // Add all to cache + cache_add(addr1); + cache_add(addr2); + cache_add(addr3); + + // All should now be in cache + EXPECT_TRUE(cache_contains(addr1)); + EXPECT_TRUE(cache_contains(addr2)); + EXPECT_TRUE(cache_contains(addr3)); +} + +TEST_F(CacheTest, SameAddressDifferentPorts) { + addr_port addr1{}, addr2{}; + addr1.addr.ip4 = 2000; + addr1.port = 80; + addr2.addr.ip4 = 2000; // Same IP + addr2.port = 443; // Different port + + cache_add(addr1); + + EXPECT_TRUE(cache_contains(addr1)); + EXPECT_FALSE(cache_contains(addr2)); // Different port should not match +} + +TEST_F(CacheTest, CacheRefreshOnAccess) { + addr_port addr{}; + addr.addr.ip4 = 3000; + addr.port = 80; + + cache_add(addr); + EXPECT_TRUE(cache_contains(addr)); + + // Access the cache multiple times - this should refresh the last_time + for (int i = 0; i < 5; i++) { + EXPECT_TRUE(cache_contains(addr)); + sleep(1); // Small delay + } + + // Should still be in cache after multiple accesses + EXPECT_TRUE(cache_contains(addr)); +} + +TEST_F(CacheTest, DuplicateAddDoesNotCrash) { + addr_port addr{}; + addr.addr.ip4 = 4000; + addr.port = 80; + + // Add the same address multiple times + cache_add(addr); + cache_add(addr); + cache_add(addr); + + EXPECT_TRUE(cache_contains(addr)); } \ No newline at end of file diff --git a/test/cli_test.cc b/test/cli_test.cc new file mode 100644 index 0000000..401fff2 --- /dev/null +++ b/test/cli_test.cc @@ -0,0 +1,64 @@ +#include +#include +#include + +extern "C" { +#include +} + +class CLITest : public ::testing::Test { +protected: + void SetUp() override { + // Capture original uid for cleanup + original_uid = geteuid(); + } + + void TearDown() override { + // Reset to original state if needed + } + + uid_t original_uid; +}; + +TEST_F(CLITest, TryPrintInfoNoArgs) { + char *argv[] = {(char*)"ua2f"}; + int argc = 1; + + // Should not crash with no arguments + EXPECT_NO_THROW(try_print_info(argc, argv)); +} + +TEST_F(CLITest, TryPrintInfoVersion) { + char *argv[] = {(char*)"ua2f", (char*)"--version"}; + int argc = 2; + + // --version should exit, but we can't easily test exit behavior in unit tests + // Just make sure the function exists and can be called without segfault + EXPECT_EXIT(try_print_info(argc, argv), ::testing::ExitedWithCode(EXIT_SUCCESS), ".*"); +} + +TEST_F(CLITest, TryPrintInfoHelp) { + char *argv[] = {(char*)"ua2f", (char*)"--help"}; + int argc = 2; + + // --help should exit with success + EXPECT_EXIT(try_print_info(argc, argv), ::testing::ExitedWithCode(EXIT_SUCCESS), ".*"); +} + +TEST_F(CLITest, TryPrintInfoUnknownOption) { + char *argv[] = {(char*)"ua2f", (char*)"--unknown"}; + int argc = 2; + + // Unknown option should exit with failure + EXPECT_EXIT(try_print_info(argc, argv), ::testing::ExitedWithCode(EXIT_FAILURE), ".*"); +} + +TEST_F(CLITest, RequireRootWhenRoot) { + if (geteuid() == 0) { + // If we're actually root, this should not exit + EXPECT_NO_THROW(require_root()); + } else { + // If we're not root, this should exit with failure + EXPECT_EXIT(require_root(), ::testing::ExitedWithCode(EXIT_FAILURE), ".*"); + } +} \ No newline at end of file diff --git a/test/statistics_test.cc b/test/statistics_test.cc new file mode 100644 index 0000000..dfaf60b --- /dev/null +++ b/test/statistics_test.cc @@ -0,0 +1,101 @@ +#include +#include + +extern "C" { +#include +} + +class StatisticsTest : public ::testing::Test { +protected: + void SetUp() override { + init_statistics(); + } +}; + +TEST_F(StatisticsTest, InitializeStatistics) { + // Statistics should be initialized without error + EXPECT_NO_THROW(init_statistics()); +} + +TEST_F(StatisticsTest, CountUserAgentPacket) { + EXPECT_NO_THROW(count_user_agent_packet()); + // Call multiple times to test counting + for (int i = 0; i < 5; i++) { + count_user_agent_packet(); + } +} + +TEST_F(StatisticsTest, CountTcpPacket) { + EXPECT_NO_THROW(count_tcp_packet()); + // Call multiple times to test counting + for (int i = 0; i < 10; i++) { + count_tcp_packet(); + } +} + +TEST_F(StatisticsTest, CountHttpPacket) { + EXPECT_NO_THROW(count_http_packet()); + // Call multiple times to test counting + for (int i = 0; i < 3; i++) { + count_http_packet(); + } +} + +TEST_F(StatisticsTest, CountIpv4Packet) { + EXPECT_NO_THROW(count_ipv4_packet()); + // Call multiple times to test counting + for (int i = 0; i < 7; i++) { + count_ipv4_packet(); + } +} + +TEST_F(StatisticsTest, CountIpv6Packet) { + EXPECT_NO_THROW(count_ipv6_packet()); + // Call multiple times to test counting + for (int i = 0; i < 4; i++) { + count_ipv6_packet(); + } +} + +TEST_F(StatisticsTest, TryPrintStatistics) { + // Should not crash when called + EXPECT_NO_THROW(try_print_statistics()); + + // Generate some statistics and try printing + for (int i = 0; i < 100; i++) { + count_user_agent_packet(); + count_tcp_packet(); + count_http_packet(); + count_ipv4_packet(); + } + EXPECT_NO_THROW(try_print_statistics()); +} + +// Test time string formatting function if accessible +extern "C" char *fill_time_string(const double sec); + +TEST_F(StatisticsTest, TimeStringFormatting) { + char *result; + + // Test seconds + result = fill_time_string(30.0); + EXPECT_TRUE(strstr(result, "seconds") != nullptr); + + // Test minutes + result = fill_time_string(150.0); + EXPECT_TRUE(strstr(result, "minutes") != nullptr); + EXPECT_TRUE(strstr(result, "seconds") != nullptr); + + // Test hours + result = fill_time_string(3700.0); + EXPECT_TRUE(strstr(result, "hours") != nullptr); + EXPECT_TRUE(strstr(result, "minutes") != nullptr); + EXPECT_TRUE(strstr(result, "seconds") != nullptr); + + // Test days + result = fill_time_string(90000.0); + EXPECT_TRUE(strstr(result, "days") != nullptr); + EXPECT_TRUE(strstr(result, "hours") != nullptr); + EXPECT_TRUE(strstr(result, "minutes") != nullptr); + EXPECT_TRUE(strstr(result, "seconds") != nullptr); +} \ No newline at end of file diff --git a/test/util_test.cc b/test/util_test.cc index 2314109..cabbf3d 100644 --- a/test/util_test.cc +++ b/test/util_test.cc @@ -84,4 +84,74 @@ TEST(HttpProtocolTest, RealWorldRequests) { // Check that these cases return false EXPECT_FALSE(is_http_protocol(invalidPayload, strlen(invalidPayload))) << "Invalid method passed"; +} + +TEST(HttpProtocolTest, AllHttpMethods) { + // Test all supported HTTP methods + EXPECT_TRUE(is_http_protocol("GET /", 5)); + EXPECT_TRUE(is_http_protocol("POST /", 6)); + EXPECT_TRUE(is_http_protocol("OPTIONS /", 9)); + EXPECT_TRUE(is_http_protocol("HEAD /", 6)); + EXPECT_TRUE(is_http_protocol("PUT /", 5)); + EXPECT_TRUE(is_http_protocol("DELETE /", 8)); + EXPECT_TRUE(is_http_protocol("TRACE /", 7)); + EXPECT_TRUE(is_http_protocol("CONNECT /", 9)); +} + +TEST(HttpProtocolTest, EdgeCases) { + // Empty payload + EXPECT_FALSE(is_http_protocol("", 0)); + + // Too short for any method + EXPECT_FALSE(is_http_protocol("G", 1)); + EXPECT_FALSE(is_http_protocol("GE", 2)); + + // Incomplete methods + EXPECT_FALSE(is_http_protocol("GE", 2)); + EXPECT_FALSE(is_http_protocol("POS", 3)); + + // Case sensitivity + EXPECT_FALSE(is_http_protocol("get /", 5)); + EXPECT_FALSE(is_http_protocol("Post /", 6)); + + // Non-HTTP protocols + EXPECT_FALSE(is_http_protocol("FTP /", 5)); + EXPECT_FALSE(is_http_protocol("SSH /", 5)); + EXPECT_FALSE(is_http_protocol("HTTPS /", 7)); +} + +TEST(MemNCaseMemTest, CaseInsensitiveSearch) { + const char *l = "HELLO WORLD"; + size_t l_len = 11; + + // Test case insensitive matching + EXPECT_EQ(memncasemem(l, l_len, "hello", 5), (void *)l); + EXPECT_EQ(memncasemem(l, l_len, "HELLO", 5), (void *)l); + EXPECT_EQ(memncasemem(l, l_len, "HeLLo", 5), (void *)l); + EXPECT_EQ(memncasemem(l, l_len, "world", 5), (void *)(l + 6)); + EXPECT_EQ(memncasemem(l, l_len, "WORLD", 5), (void *)(l + 6)); + EXPECT_EQ(memncasemem(l, l_len, "WoRLd", 5), (void *)(l + 6)); +} + +TEST(MemNCaseMemTest, NotFoundCases) { + const char *l = "Hello World"; + size_t l_len = 11; + + // Search for non-existent strings + EXPECT_EQ(memncasemem(l, l_len, "xyz", 3), nullptr); + EXPECT_EQ(memncasemem(l, l_len, "foo", 3), nullptr); + EXPECT_EQ(memncasemem(l, l_len, "hello world!", 12), nullptr); // Longer than haystack +} + +TEST(MemNCaseMemTest, MultipleOccurrences) { + const char *l = "hello hello hello"; + size_t l_len = 17; + + // Should find the first occurrence + void *result = memncasemem(l, l_len, "hello", 5); + EXPECT_EQ(result, (void *)l); + + // Search from different starting positions + void *result2 = memncasemem(l + 6, l_len - 6, "hello", 5); + EXPECT_EQ(result2, (void *)(l + 6)); } \ No newline at end of file