/** * @file WebModuleTests.cpp * @brief Integration tests for WebModule (10 TI) */ #include #include "mocks/MockIO.hpp" #include "utils/TimeSimulator.hpp" #include "utils/TestHelpers.hpp" #include "modules/WebModule.h" #include using namespace aissia; using namespace aissia::tests; // ============================================================================ // Test Fixture // ============================================================================ class WebTestFixture { public: MockIO io; TimeSimulator time; WebModule module; void configure(const json& config = json::object()) { json fullConfig = { {"enabled", true}, {"requestTimeoutMs", 5000}, {"maxConcurrentRequests", 10} }; fullConfig.merge_patch(config); grove::JsonDataNode configNode("config", fullConfig); module.setConfiguration(configNode, &io, nullptr); } void process() { grove::JsonDataNode input("input", time.createInput()); module.process(input); } }; // ============================================================================ // TI_WEB_001: Simple GET Request // ============================================================================ TEST_CASE("TI_WEB_001_SimpleGetRequest", "[web][integration]") { WebTestFixture f; f.configure(); // Send GET request (we use a public API that should work) f.io.injectMessage("web:request", { {"requestId", "test-001"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.process(); // Verify response published REQUIRE(f.io.wasPublished("web:response")); auto response = f.io.getLastPublished("web:response"); REQUIRE(response["requestId"] == "test-001"); REQUIRE(response.contains("success")); REQUIRE(response.contains("durationMs")); } // ============================================================================ // TI_WEB_002: POST Request with Body // ============================================================================ TEST_CASE("TI_WEB_002_PostRequestWithBody", "[web][integration]") { WebTestFixture f; f.configure(); // Send POST request f.io.injectMessage("web:request", { {"requestId", "test-002"}, {"url", "https://httpbin.org/post"}, {"method", "POST"}, {"body", R"({"key": "value"})"} }); f.process(); // Verify response REQUIRE(f.io.wasPublished("web:response")); auto response = f.io.getLastPublished("web:response"); REQUIRE(response["requestId"] == "test-002"); REQUIRE(response.contains("success")); } // ============================================================================ // TI_WEB_003: Invalid URL Handling // ============================================================================ TEST_CASE("TI_WEB_003_InvalidUrlHandling", "[web][integration]") { WebTestFixture f; f.configure(); // Send request with invalid URL f.io.injectMessage("web:request", { {"requestId", "test-003"}, {"url", ""}, {"method", "GET"} }); f.process(); // Verify error response REQUIRE(f.io.wasPublished("web:response")); auto response = f.io.getLastPublished("web:response"); REQUIRE(response["requestId"] == "test-003"); REQUIRE(response["success"] == false); REQUIRE(response["errorCode"] == "INVALID_URL"); } // ============================================================================ // TI_WEB_004: Timeout Handling // ============================================================================ TEST_CASE("TI_WEB_004_TimeoutHandling", "[web][integration]") { WebTestFixture f; f.configure(); // Send request with very short timeout to a slow endpoint f.io.injectMessage("web:request", { {"requestId", "test-004"}, {"url", "https://httpbin.org/delay/10"}, {"method", "GET"}, {"timeoutMs", 100} }); f.process(); // Verify response (should timeout or fail) REQUIRE(f.io.wasPublished("web:response")); auto response = f.io.getLastPublished("web:response"); REQUIRE(response["requestId"] == "test-004"); // Either timeout or connection error REQUIRE(response.contains("success")); } // ============================================================================ // TI_WEB_005: Multiple Concurrent Requests // ============================================================================ TEST_CASE("TI_WEB_005_MultipleConcurrentRequests", "[web][integration]") { WebTestFixture f; f.configure(); // Send multiple requests f.io.injectMessage("web:request", { {"requestId", "test-005-a"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.io.injectMessage("web:request", { {"requestId", "test-005-b"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.io.injectMessage("web:request", { {"requestId", "test-005-c"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.process(); // Verify all responses REQUIRE(f.io.countPublished("web:response") >= 3); auto messages = f.io.getAllPublished("web:response"); REQUIRE(messages.size() >= 3); } // ============================================================================ // TI_WEB_006: Request ID Tracking // ============================================================================ TEST_CASE("TI_WEB_006_RequestIdTracking", "[web][integration]") { WebTestFixture f; f.configure(); // Send request with specific ID std::string testId = "unique-request-id-12345"; f.io.injectMessage("web:request", { {"requestId", testId}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.process(); // Verify response has same ID REQUIRE(f.io.wasPublished("web:response")); auto response = f.io.getLastPublished("web:response"); REQUIRE(response["requestId"] == testId); } // ============================================================================ // TI_WEB_007: Statistics Tracking // ============================================================================ TEST_CASE("TI_WEB_007_StatisticsTracking", "[web][integration]") { WebTestFixture f; f.configure(); // Initial health check auto health1 = f.module.getHealthStatus(); int initialTotal = health1->getInt("totalRequests", 0); // Send successful request f.io.injectMessage("web:request", { {"requestId", "test-007"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.process(); // Check health status auto health2 = f.module.getHealthStatus(); REQUIRE(health2->getInt("totalRequests", 0) == initialTotal + 1); REQUIRE(health2->getInt("successfulRequests", 0) >= 0); } // ============================================================================ // TI_WEB_008: State Serialization // ============================================================================ TEST_CASE("TI_WEB_008_StateSerialization", "[web][integration]") { WebTestFixture f; f.configure(); // Make some requests f.io.injectMessage("web:request", { {"requestId", "test-008"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.process(); // Get state auto state = f.module.getState(); REQUIRE(state != nullptr); int totalRequests = state->getInt("totalRequests", 0); REQUIRE(totalRequests > 0); // Create new module and restore state WebModule newModule; grove::JsonDataNode emptyConfig("config"); newModule.setConfiguration(emptyConfig, &f.io, nullptr); newModule.setState(*state); // Verify state restored auto restoredHealth = newModule.getHealthStatus(); REQUIRE(restoredHealth->getInt("totalRequests", 0) == totalRequests); } // ============================================================================ // TI_WEB_009: Configuration Loading // ============================================================================ TEST_CASE("TI_WEB_009_ConfigurationLoading", "[web][integration]") { WebTestFixture f; // Configure with custom values f.configure({ {"enabled", false}, {"requestTimeoutMs", 15000}, {"maxConcurrentRequests", 5} }); // Verify module respects enabled flag f.io.injectMessage("web:request", { {"requestId", "test-009"}, {"url", "https://api.github.com"}, {"method", "GET"} }); f.process(); // Should not process when disabled REQUIRE(f.io.countPublished("web:response") == 0); } // ============================================================================ // TI_WEB_010: Error Response Format // ============================================================================ TEST_CASE("TI_WEB_010_ErrorResponseFormat", "[web][integration]") { WebTestFixture f; f.configure(); // Send request with blocked URL (localhost) f.io.injectMessage("web:request", { {"requestId", "test-010"}, {"url", "http://localhost:8080/test"}, {"method", "GET"} }); f.process(); // Verify error response format REQUIRE(f.io.wasPublished("web:response")); auto response = f.io.getLastPublished("web:response"); REQUIRE(response["requestId"] == "test-010"); REQUIRE(response["success"] == false); REQUIRE(response.contains("error")); REQUIRE(response.contains("errorCode")); REQUIRE(response["errorCode"] == "BLOCKED_URL"); }