/** * @file StorageModuleTests.cpp * @brief Integration tests for StorageModule (10 TI) */ #include #include "mocks/MockIO.hpp" #include "utils/TimeSimulator.hpp" #include "utils/TestHelpers.hpp" #include "modules/StorageModule.h" #include using namespace aissia; using namespace aissia::tests; // ============================================================================ // Test Fixture // ============================================================================ class StorageTestFixture { public: MockIO io; TimeSimulator time; StorageModule module; void configure(const json& config = json::object()) { json fullConfig = json::object(); fullConfig.merge_patch(config); grove::JsonDataNode configNode(fullConfig); module.setConfiguration(configNode, &io, nullptr); } void process() { grove::JsonDataNode input(time.createInput()); module.process(input); } }; // ============================================================================ // TI_STORAGE_001: Task Completed Saves Session // ============================================================================ TEST_CASE("TI_STORAGE_001_TaskCompletedSavesSession", "[storage][integration]") { StorageTestFixture f; f.configure(); // Receive task completed f.io.injectMessage("scheduler:task_completed", { {"taskId", "task-1"}, {"taskName", "Coding session"}, {"durationMinutes", 45}, {"hyperfocus", false} }); f.process(); // Verify save_session published REQUIRE(f.io.wasPublished("storage:save_session")); auto msg = f.io.getLastPublished("storage:save_session"); REQUIRE(msg["taskName"] == "Coding session"); REQUIRE(msg["durationMinutes"] == 45); } // ============================================================================ // TI_STORAGE_002: App Changed Saves Usage // ============================================================================ TEST_CASE("TI_STORAGE_002_AppChangedSavesUsage", "[storage][integration]") { StorageTestFixture f; f.configure(); // Receive app changed with duration f.io.injectMessage("monitoring:app_changed", { {"appName", "Code"}, {"oldApp", "Discord"}, {"duration", 120}, {"classification", "productive"} }); f.process(); // Verify save_app_usage published REQUIRE(f.io.wasPublished("storage:save_app_usage")); auto msg = f.io.getLastPublished("storage:save_app_usage"); REQUIRE(msg["appName"] == "Discord"); // Old app that ended REQUIRE(msg["durationSeconds"] == 120); } // ============================================================================ // TI_STORAGE_003: Session Saved Updates Last ID // ============================================================================ TEST_CASE("TI_STORAGE_003_SessionSavedUpdatesLastId", "[storage][integration]") { StorageTestFixture f; f.configure(); // Receive session saved confirmation f.io.injectMessage("storage:session_saved", { {"sessionId", 42} }); f.process(); // Verify state updated auto state = f.module.getState(); // TODO: Verify lastSessionId == 42 SUCCEED(); // Placeholder } // ============================================================================ // TI_STORAGE_004: Storage Error Handled // ============================================================================ TEST_CASE("TI_STORAGE_004_StorageErrorHandled", "[storage][integration]") { StorageTestFixture f; f.configure(); // Receive storage error f.io.injectMessage("storage:error", { {"message", "Database locked"} }); // Should not throw REQUIRE_NOTHROW(f.process()); } // ============================================================================ // TI_STORAGE_005: Pending Saves Tracking // ============================================================================ TEST_CASE("TI_STORAGE_005_PendingSavesTracking", "[storage][integration]") { StorageTestFixture f; f.configure(); // Trigger save f.io.injectMessage("scheduler:task_completed", { {"taskId", "t1"}, {"taskName", "Task"}, {"durationMinutes", 10} }); f.process(); // Verify pending incremented auto state = f.module.getState(); // TODO: Verify pendingSaves == 1 SUCCEED(); // Placeholder } // ============================================================================ // TI_STORAGE_006: Total Saved Tracking // ============================================================================ TEST_CASE("TI_STORAGE_006_TotalSavedTracking", "[storage][integration]") { StorageTestFixture f; f.configure(); // Save and confirm multiple times for (int i = 0; i < 3; i++) { f.io.injectMessage("scheduler:task_completed", { {"taskId", "t" + std::to_string(i)}, {"taskName", "Task"}, {"durationMinutes", 10} }); f.process(); f.io.injectMessage("storage:session_saved", {{"sessionId", i}}); f.process(); } // Verify total auto state = f.module.getState(); // TODO: Verify totalSaved == 3 SUCCEED(); // Placeholder } // ============================================================================ // TI_STORAGE_007: Tool Query Notes // ============================================================================ TEST_CASE("TI_STORAGE_007_ToolQueryNotes", "[storage][integration]") { StorageTestFixture f; f.configure(); // Add a note first f.io.injectMessage("storage:command", { {"action", "save_note"}, {"content", "Test note"}, {"tags", json::array({"test", "important"})} }); f.process(); f.io.clearPublished(); // Query notes f.io.injectMessage("storage:query", { {"action", "query_notes"}, {"correlation_id", "query-1"} }); f.process(); // Verify response REQUIRE(f.io.wasPublished("storage:response")); auto resp = f.io.getLastPublished("storage:response"); REQUIRE(resp["correlation_id"] == "query-1"); } // ============================================================================ // TI_STORAGE_008: Tool Command Save Note // ============================================================================ TEST_CASE("TI_STORAGE_008_ToolCommandSaveNote", "[storage][integration]") { StorageTestFixture f; f.configure(); // Save note f.io.injectMessage("storage:command", { {"action", "save_note"}, {"content", "Remember to check logs"}, {"tags", json::array({"reminder"})} }); f.process(); // Verify note added to state auto state = f.module.getState(); // TODO: Verify notes contains the new note SUCCEED(); // Placeholder } // ============================================================================ // TI_STORAGE_009: Note Tags Filtering // ============================================================================ TEST_CASE("TI_STORAGE_009_NoteTagsFiltering", "[storage][integration]") { StorageTestFixture f; f.configure(); // Add notes with different tags f.io.injectMessage("storage:command", { {"action", "save_note"}, {"content", "Work note"}, {"tags", json::array({"work"})} }); f.process(); f.io.injectMessage("storage:command", { {"action", "save_note"}, {"content", "Personal note"}, {"tags", json::array({"personal"})} }); f.process(); f.io.clearPublished(); // Query with tag filter f.io.injectMessage("storage:query", { {"action", "query_notes"}, {"tags", json::array({"work"})}, {"correlation_id", "filter-1"} }); f.process(); // Verify filtered response REQUIRE(f.io.wasPublished("storage:response")); auto resp = f.io.getLastPublished("storage:response"); // TODO: Verify only work notes returned SUCCEED(); // Placeholder } // ============================================================================ // TI_STORAGE_010: State Serialization // ============================================================================ TEST_CASE("TI_STORAGE_010_StateSerialization", "[storage][integration]") { StorageTestFixture f; f.configure(); // Build state with notes f.io.injectMessage("storage:command", { {"action", "save_note"}, {"content", "Test note for serialization"}, {"tags", json::array({"test"})} }); f.process(); // Get state auto state = f.module.getState(); REQUIRE(state != nullptr); // Restore StorageModule module2; grove::JsonDataNode configNode(json::object()); module2.setConfiguration(configNode, &f.io, nullptr); module2.setState(*state); auto state2 = module2.getState(); REQUIRE(state2 != nullptr); SUCCEED(); // Placeholder }