/** * @file MonitoringModuleTests.cpp * @brief Integration tests for MonitoringModule (10 TI) */ #include #include "mocks/MockIO.hpp" #include "utils/TimeSimulator.hpp" #include "utils/TestHelpers.hpp" #include "modules/MonitoringModule.h" #include using namespace aissia; using namespace aissia::tests; // ============================================================================ // Test Fixture // ============================================================================ class MonitoringTestFixture { public: MockIO io; TimeSimulator time; MonitoringModule module; void configure(const json& config = json::object()) { json fullConfig = { {"enabled", true}, {"productive_apps", json::array({"Code", "CLion", "Visual Studio"})}, {"distracting_apps", json::array({"Discord", "Steam", "YouTube"})} }; fullConfig.merge_patch(config); grove::JsonDataNode configNode(fullConfig); module.setConfiguration(configNode, &io, nullptr); } void process() { grove::JsonDataNode input(time.createInput()); module.process(input); } }; // ============================================================================ // TI_MONITOR_001: App Changed // ============================================================================ TEST_CASE("TI_MONITOR_001_AppChanged", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Inject window change f.io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Code"}, {"duration", 0} }); f.process(); // Verify app_changed published REQUIRE(f.io.wasPublished("monitoring:app_changed")); auto msg = f.io.getLastPublished("monitoring:app_changed"); REQUIRE(msg["appName"] == "Code"); } // ============================================================================ // TI_MONITOR_002: Productive App Classification // ============================================================================ TEST_CASE("TI_MONITOR_002_ProductiveAppClassification", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); f.io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Code"}, {"duration", 0} }); f.process(); REQUIRE(f.io.wasPublished("monitoring:app_changed")); auto msg = f.io.getLastPublished("monitoring:app_changed"); REQUIRE(msg["classification"] == "productive"); } // ============================================================================ // TI_MONITOR_003: Distracting App Classification // ============================================================================ TEST_CASE("TI_MONITOR_003_DistractingAppClassification", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); f.io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Discord"}, {"duration", 0} }); f.process(); REQUIRE(f.io.wasPublished("monitoring:app_changed")); auto msg = f.io.getLastPublished("monitoring:app_changed"); REQUIRE(msg["classification"] == "distracting"); } // ============================================================================ // TI_MONITOR_004: Neutral App Classification // ============================================================================ TEST_CASE("TI_MONITOR_004_NeutralAppClassification", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); f.io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Notepad"}, {"duration", 0} }); f.process(); REQUIRE(f.io.wasPublished("monitoring:app_changed")); auto msg = f.io.getLastPublished("monitoring:app_changed"); REQUIRE(msg["classification"] == "neutral"); } // ============================================================================ // TI_MONITOR_005: Duration Tracking // ============================================================================ TEST_CASE("TI_MONITOR_005_DurationTracking", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Start with Code f.io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Code"}, {"duration", 0} }); f.process(); f.io.clearPublished(); // Switch after 60 seconds f.io.injectMessage("platform:window_changed", { {"oldApp", "Code"}, {"newApp", "Discord"}, {"duration", 60} }); f.process(); // Verify duration tracked auto state = f.module.getState(); // TODO: Verify appDurations["Code"] == 60 SUCCEED(); // Placeholder } // ============================================================================ // TI_MONITOR_006: Idle Detected Pauses Tracking // ============================================================================ TEST_CASE("TI_MONITOR_006_IdleDetectedPausesTracking", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Start tracking f.io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Code"}, {"duration", 0} }); f.process(); // Go idle f.io.injectMessage("platform:idle_detected", {{"idleSeconds", 300}}); f.process(); // Verify idle state auto state = f.module.getState(); // TODO: Verify isIdle == true SUCCEED(); // Placeholder } // ============================================================================ // TI_MONITOR_007: Activity Resumed Resumes Tracking // ============================================================================ TEST_CASE("TI_MONITOR_007_ActivityResumedResumesTracking", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Setup idle state f.io.injectMessage("platform:window_changed", {{"oldApp", ""}, {"newApp", "Code"}, {"duration", 0}}); f.process(); f.io.injectMessage("platform:idle_detected", {}); f.process(); // Resume f.io.injectMessage("platform:activity_resumed", {}); f.process(); // Verify not idle auto state = f.module.getState(); // TODO: Verify isIdle == false SUCCEED(); // Placeholder } // ============================================================================ // TI_MONITOR_008: Productivity Stats // ============================================================================ TEST_CASE("TI_MONITOR_008_ProductivityStats", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Use productive app for 60s f.io.injectMessage("platform:window_changed", {{"oldApp", ""}, {"newApp", "Code"}, {"duration", 0}}); f.process(); f.io.injectMessage("platform:window_changed", {{"oldApp", "Code"}, {"newApp", "Discord"}, {"duration", 60}}); f.process(); // Use distracting app for 30s f.io.injectMessage("platform:window_changed", {{"oldApp", "Discord"}, {"newApp", "Code"}, {"duration", 30}}); f.process(); // Verify stats auto state = f.module.getState(); // TODO: Verify totalProductiveSeconds == 60, totalDistractingSeconds == 30 SUCCEED(); // Placeholder } // ============================================================================ // TI_MONITOR_009: Tool Query Get Current App // ============================================================================ TEST_CASE("TI_MONITOR_009_ToolQueryGetCurrentApp", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Set current app f.io.injectMessage("platform:window_changed", {{"oldApp", ""}, {"newApp", "Code"}, {"duration", 0}}); f.process(); f.io.clearPublished(); // Query f.io.injectMessage("monitoring:query", { {"action", "get_current_app"}, {"correlation_id", "test-456"} }); f.process(); // Verify response REQUIRE(f.io.wasPublished("monitoring:response")); auto resp = f.io.getLastPublished("monitoring:response"); REQUIRE(resp["correlation_id"] == "test-456"); } // ============================================================================ // TI_MONITOR_010: State Serialization // ============================================================================ TEST_CASE("TI_MONITOR_010_StateSerialization", "[monitoring][integration]") { MonitoringTestFixture f; f.configure(); // Build up some state f.io.injectMessage("platform:window_changed", {{"oldApp", ""}, {"newApp", "Code"}, {"duration", 0}}); f.process(); f.io.injectMessage("platform:window_changed", {{"oldApp", "Code"}, {"newApp", "Discord"}, {"duration", 120}}); f.process(); // Get state auto state = f.module.getState(); REQUIRE(state != nullptr); // Restore to new module MonitoringModule module2; grove::JsonDataNode configNode(json::object()); module2.setConfiguration(configNode, &f.io, nullptr); module2.setState(*state); auto state2 = module2.getState(); REQUIRE(state2 != nullptr); SUCCEED(); // Placeholder }