# Plan d'Implementation - Tests d'Integration AISSIA ## Vue d'Ensemble Ce document decrit le plan complet pour implementer 110 tests d'integration (TI) : - 10 TI par module (6 modules = 60 TI) - 50 TI pour le systeme MCP ## Architecture des Tests ``` tests/ ├── CMakeLists.txt # Configuration tests ├── main.cpp # Entry point Catch2 ├── mocks/ │ ├── MockIO.hpp # Mock IIO pub/sub │ ├── MockDataNode.hpp # Mock IDataNode │ ├── MockTaskScheduler.hpp # Mock ITaskScheduler │ ├── MockTransport.hpp # Mock IMCPTransport │ └── MockLLMProvider.hpp # Mock ILLMProvider ├── utils/ │ ├── TestHelpers.hpp # Utilitaires communs │ ├── MessageCapture.hpp # Capture messages IIO │ └── TimeSimulator.hpp # Simulation temps (gameTime) ├── modules/ │ ├── SchedulerModuleTests.cpp # 10 TI │ ├── NotificationModuleTests.cpp # 10 TI │ ├── MonitoringModuleTests.cpp # 10 TI │ ├── AIModuleTests.cpp # 10 TI │ ├── VoiceModuleTests.cpp # 10 TI │ └── StorageModuleTests.cpp # 10 TI └── mcp/ ├── MCPTypesTests.cpp # 15 TI ├── StdioTransportTests.cpp # 20 TI └── MCPClientTests.cpp # 15 TI ``` --- ## Phase 1: Infrastructure (Priorite Haute) ### 1.1 Mocks Essentiels #### MockIO.hpp ```cpp class MockIO : public grove::IIO { public: // Capture des messages publies std::vector> publishedMessages; // Queue de messages a recevoir std::queue incomingMessages; void publish(const std::string& topic, const grove::IDataNode& data) override; bool hasMessages() const override; grove::Message popMessage() override; // Helpers de test void injectMessage(const std::string& topic, const json& data); bool wasPublished(const std::string& topic) const; json getLastPublished(const std::string& topic) const; void clear(); }; ``` #### MockTransport.hpp ```cpp class MockTransport : public aissia::mcp::IMCPTransport { public: bool m_running = false; std::vector sentRequests; std::queue preparedResponses; bool start() override; void stop() override; bool isRunning() const override; JsonRpcResponse sendRequest(const JsonRpcRequest& request, int timeoutMs) override; // Test helpers void prepareResponse(const JsonRpcResponse& response); void setStartFailure(bool fail); }; ``` ### 1.2 Utilitaires #### TimeSimulator.hpp ```cpp class TimeSimulator { public: float m_gameTime = 0.0f; json createInput(float deltaTime = 0.1f); void advance(float seconds); void setTime(float time); }; ``` #### MessageCapture.hpp ```cpp class MessageCapture { public: void captureFrom(MockIO& io); bool waitForMessage(const std::string& topic, int timeoutMs = 1000); json getMessage(const std::string& topic); int countMessages(const std::string& topic); }; ``` --- ## Phase 2: Tests des Modules (60 TI) ### 2.1 SchedulerModule (10 TI) | # | Test | Description | Topics Verifies | |---|------|-------------|-----------------| | 1 | `TI_SCHEDULER_001_StartTask` | Demarrer une tache publie `scheduler:task_started` | `scheduler:task_started` | | 2 | `TI_SCHEDULER_002_CompleteTask` | Completer une tache publie `scheduler:task_completed` avec duree | `scheduler:task_completed` | | 3 | `TI_SCHEDULER_003_HyperfocusDetection` | Session > 120min declenche `scheduler:hyperfocus_alert` | `scheduler:hyperfocus_alert` | | 4 | `TI_SCHEDULER_004_HyperfocusAlertOnce` | Alerte hyperfocus envoyee une seule fois par session | single publish | | 5 | `TI_SCHEDULER_005_BreakReminder` | Rappel de pause toutes les 45min | `scheduler:break_reminder` | | 6 | `TI_SCHEDULER_006_IdlePausesSession` | Reception `monitoring:idle_detected` pause le tracking | state | | 7 | `TI_SCHEDULER_007_ActivityResumesSession` | Reception `monitoring:activity_resumed` reprend le tracking | state | | 8 | `TI_SCHEDULER_008_ToolQueryGetCurrentTask` | Query tool `get_current_task` retourne tache courante | `scheduler:response` | | 9 | `TI_SCHEDULER_009_ToolCommandStartBreak` | Command tool `start_break` publie `scheduler:break_started` | `scheduler:break_started` | | 10 | `TI_SCHEDULER_010_StateSerialization` | `getState()` et `setState()` preservent l'etat complet | state roundtrip | **Implementation:** ```cpp TEST_CASE("TI_SCHEDULER_001_StartTask", "[scheduler][integration]") { MockIO io; SchedulerModule module; TimeSimulator time; // Configure json config = {{"hyperfocusThresholdMinutes", 120}}; module.setConfiguration(JsonDataNode(config), &io, nullptr); // Add task io.injectMessage("user:task_switch", {{"taskId", "task-1"}}); // Process module.process(JsonDataNode(time.createInput())); // Verify REQUIRE(io.wasPublished("scheduler:task_started")); auto msg = io.getLastPublished("scheduler:task_started"); REQUIRE(msg["taskId"] == "task-1"); } ``` --- ### 2.2 NotificationModule (10 TI) | # | Test | Description | Topics Verifies | |---|------|-------------|-----------------| | 1 | `TI_NOTIF_001_QueueNotification` | `notify()` ajoute a la queue | state queue size | | 2 | `TI_NOTIF_002_ProcessQueue` | `process()` traite max 3 notifications/frame | queue drain | | 3 | `TI_NOTIF_003_PriorityOrdering` | URGENT traite avant NORMAL | order | | 4 | `TI_NOTIF_004_SilentModeBlocksNonUrgent` | Mode silencieux bloque LOW/NORMAL/HIGH | filtering | | 5 | `TI_NOTIF_005_SilentModeAllowsUrgent` | Mode silencieux laisse passer URGENT | filtering | | 6 | `TI_NOTIF_006_MaxQueueSize` | Queue limitee a `maxQueueSize` (50) | overflow | | 7 | `TI_NOTIF_007_LanguageConfig` | Langue configuree via `setConfiguration` | config | | 8 | `TI_NOTIF_008_NotificationCountTracking` | Compteurs `notificationCount` et `urgentCount` | state | | 9 | `TI_NOTIF_009_StateSerialization` | `getState()`/`setState()` preservent queue et compteurs | state roundtrip | | 10 | `TI_NOTIF_010_MultipleFrameProcessing` | Queue > 3 elements necessite plusieurs frames | multi-frame | **Implementation:** ```cpp TEST_CASE("TI_NOTIF_004_SilentModeBlocksNonUrgent", "[notification][integration]") { MockIO io; NotificationModule module; TimeSimulator time; json config = {{"silentMode", true}}; module.setConfiguration(JsonDataNode(config), &io, nullptr); module.notify("Test", "Normal message", NotificationModule::Priority::NORMAL); module.notify("Test", "High message", NotificationModule::Priority::HIGH); // State should show 0 pending (blocked) auto state = module.getState(); REQUIRE(state->getInt("pendingCount") == 0); } ``` --- ### 2.3 MonitoringModule (10 TI) | # | Test | Description | Topics Verifies | |---|------|-------------|-----------------| | 1 | `TI_MONITOR_001_AppChanged` | Reception `platform:window_changed` publie `monitoring:app_changed` | `monitoring:app_changed` | | 2 | `TI_MONITOR_002_ProductiveAppClassification` | Apps dans `productiveApps` classees "productive" | classification | | 3 | `TI_MONITOR_003_DistractingAppClassification` | Apps dans `distractingApps` classees "distracting" | classification | | 4 | `TI_MONITOR_004_NeutralAppClassification` | Apps inconnues classees "neutral" | classification | | 5 | `TI_MONITOR_005_DurationTracking` | `m_appDurations` accumule temps par app | duration map | | 6 | `TI_MONITOR_006_IdleDetectedPausesTracking` | `platform:idle_detected` pause accumulation | state | | 7 | `TI_MONITOR_007_ActivityResumedResumesTracking` | `platform:activity_resumed` reprend accumulation | state | | 8 | `TI_MONITOR_008_ProductivityStats` | `m_totalProductiveSeconds` et `m_totalDistractingSeconds` corrects | stats | | 9 | `TI_MONITOR_009_ToolQueryGetCurrentApp` | Query `get_current_app` retourne app courante | `monitoring:response` | | 10 | `TI_MONITOR_010_StateSerialization` | `getState()`/`setState()` preservent stats et durations | state roundtrip | **Implementation:** ```cpp TEST_CASE("TI_MONITOR_002_ProductiveAppClassification", "[monitoring][integration]") { MockIO io; MonitoringModule module; TimeSimulator time; json config = { {"productive_apps", {"Code", "CLion", "Visual Studio"}} }; module.setConfiguration(JsonDataNode(config), &io, nullptr); io.injectMessage("platform:window_changed", { {"oldApp", ""}, {"newApp", "Code"}, {"duration", 0} }); module.process(JsonDataNode(time.createInput())); REQUIRE(io.wasPublished("monitoring:app_changed")); auto msg = io.getLastPublished("monitoring:app_changed"); REQUIRE(msg["classification"] == "productive"); } ``` --- ### 2.4 AIModule (10 TI) | # | Test | Description | Topics Verifies | |---|------|-------------|-----------------| | 1 | `TI_AI_001_QuerySendsLLMRequest` | Reception `ai:query` publie `llm:request` | `llm:request` | | 2 | `TI_AI_002_VoiceTranscriptionTriggersQuery` | Reception `voice:transcription` envoie query | `llm:request` | | 3 | `TI_AI_003_LLMResponseHandled` | Reception `llm:response` met `m_awaitingResponse = false` | state | | 4 | `TI_AI_004_LLMErrorHandled` | Reception `llm:error` met `m_awaitingResponse = false` | state | | 5 | `TI_AI_005_HyperfocusAlertGeneratesSuggestion` | `scheduler:hyperfocus_alert` publie `ai:suggestion` | `ai:suggestion` | | 6 | `TI_AI_006_BreakReminderGeneratesSuggestion` | `scheduler:break_reminder` publie `ai:suggestion` | `ai:suggestion` | | 7 | `TI_AI_007_SystemPromptInRequest` | `llm:request` contient `systemPrompt` de config | request content | | 8 | `TI_AI_008_ConversationIdTracking` | Requetes utilisent `m_currentConversationId` | conversation | | 9 | `TI_AI_009_TokenCountingAccumulates` | `m_totalTokens` s'accumule apres chaque reponse | state | | 10 | `TI_AI_010_StateSerialization` | `getState()`/`setState()` preservent compteurs et conversation | state roundtrip | **Implementation:** ```cpp TEST_CASE("TI_AI_005_HyperfocusAlertGeneratesSuggestion", "[ai][integration]") { MockIO io; AIModule module; TimeSimulator time; json config = {{"system_prompt", "Tu es un assistant"}}; module.setConfiguration(JsonDataNode(config), &io, nullptr); io.injectMessage("scheduler:hyperfocus_alert", { {"sessionMinutes", 130}, {"task", "coding"} }); module.process(JsonDataNode(time.createInput())); REQUIRE(io.wasPublished("ai:suggestion")); auto msg = io.getLastPublished("ai:suggestion"); REQUIRE(msg.contains("message")); } ``` --- ### 2.5 VoiceModule (10 TI) | # | Test | Description | Topics Verifies | |---|------|-------------|-----------------| | 1 | `TI_VOICE_001_AIResponseTriggersSpeak` | Reception `ai:response` publie `voice:speak` | `voice:speak` | | 2 | `TI_VOICE_002_SuggestionPrioritySpeak` | Reception `ai:suggestion` publie `voice:speak` avec priorite | `voice:speak` priority | | 3 | `TI_VOICE_003_SpeakingStartedUpdatesState` | `voice:speaking_started` met `m_isSpeaking = true` | state | | 4 | `TI_VOICE_004_SpeakingEndedUpdatesState` | `voice:speaking_ended` met `m_isSpeaking = false` | state | | 5 | `TI_VOICE_005_IsIdleReflectsSpeaking` | `isIdle()` retourne `!m_isSpeaking` | interface | | 6 | `TI_VOICE_006_TranscriptionForwarded` | `voice:transcription` non traite par VoiceModule (forward only) | no re-publish | | 7 | `TI_VOICE_007_TotalSpokenIncremented` | `m_totalSpoken` incremente apres chaque `speaking_ended` | counter | | 8 | `TI_VOICE_008_TTSDisabledConfig` | Config `ttsEnabled: false` empeche `voice:speak` | config | | 9 | `TI_VOICE_009_ToolCommandSpeak` | Command tool `speak` publie `voice:speak` | tool | | 10 | `TI_VOICE_010_StateSerialization` | `getState()`/`setState()` preservent compteurs | state roundtrip | **Implementation:** ```cpp TEST_CASE("TI_VOICE_002_SuggestionPrioritySpeak", "[voice][integration]") { MockIO io; VoiceModule module; TimeSimulator time; json config = {{"ttsEnabled", true}}; module.setConfiguration(JsonDataNode(config), &io, nullptr); io.injectMessage("ai:suggestion", { {"message", "Tu devrais faire une pause"}, {"duration", 5} }); module.process(JsonDataNode(time.createInput())); REQUIRE(io.wasPublished("voice:speak")); auto msg = io.getLastPublished("voice:speak"); REQUIRE(msg["priority"] == true); } ``` --- ### 2.6 StorageModule (10 TI) | # | Test | Description | Topics Verifies | |---|------|-------------|-----------------| | 1 | `TI_STORAGE_001_TaskCompletedSavesSession` | `scheduler:task_completed` publie `storage:save_session` | `storage:save_session` | | 2 | `TI_STORAGE_002_AppChangedSavesUsage` | `monitoring:app_changed` publie `storage:save_app_usage` | `storage:save_app_usage` | | 3 | `TI_STORAGE_003_SessionSavedUpdatesLastId` | `storage:session_saved` met a jour `m_lastSessionId` | state | | 4 | `TI_STORAGE_004_StorageErrorHandled` | `storage:error` log l'erreur sans crash | error handling | | 5 | `TI_STORAGE_005_PendingSavesTracking` | `m_pendingSaves` incremente/decremente correctement | counter | | 6 | `TI_STORAGE_006_TotalSavedTracking` | `m_totalSaved` s'accumule | counter | | 7 | `TI_STORAGE_007_ToolQueryNotes` | Query `query_notes` retourne notes filtrees | `storage:response` | | 8 | `TI_STORAGE_008_ToolCommandSaveNote` | Command `save_note` ajoute note a `m_notes` | state | | 9 | `TI_STORAGE_009_NoteTagsFiltering` | Query notes avec tags filtre correctement | filtering | | 10 | `TI_STORAGE_010_StateSerialization` | `getState()`/`setState()` preservent notes et compteurs | state roundtrip | --- ## Phase 3: Tests MCP (50 TI) ### 3.1 MCPTypes (15 TI) | # | Test | Description | |---|------|-------------| | 1 | `TI_TYPES_001_MCPToolToJson` | `MCPTool::toJson()` serialise correctement | | 2 | `TI_TYPES_002_MCPToolFromJson` | `MCPTool::fromJson()` deserialise correctement | | 3 | `TI_TYPES_003_MCPToolFromJsonMissingFields` | `fromJson()` avec champs manquants utilise defauts | | 4 | `TI_TYPES_004_MCPResourceFromJson` | `MCPResource::fromJson()` deserialise correctement | | 5 | `TI_TYPES_005_MCPToolResultToJson` | `MCPToolResult::toJson()` serialise content et isError | | 6 | `TI_TYPES_006_MCPCapabilitiesFromJson` | Detection correcte des capabilities | | 7 | `TI_TYPES_007_MCPCapabilitiesEmpty` | Capabilities vides si pas de champs | | 8 | `TI_TYPES_008_MCPServerInfoFromJson` | `MCPServerInfo::fromJson()` parse name/version/caps | | 9 | `TI_TYPES_009_JsonRpcRequestToJson` | Serialisation avec/sans params | | 10 | `TI_TYPES_010_JsonRpcResponseFromJson` | Parse result ou error | | 11 | `TI_TYPES_011_JsonRpcResponseIsError` | `isError()` detecte presence de error | | 12 | `TI_TYPES_012_MCPServerConfigFromJson` | Parse command, args, env, enabled | | 13 | `TI_TYPES_013_MCPServerConfigEnvExpansion` | Variables env `${VAR}` expandees | | 14 | `TI_TYPES_014_MCPServerConfigDisabled` | `enabled: false` honore | | 15 | `TI_TYPES_015_JsonRpcRequestIdIncrement` | IDs uniques et croissants | **Implementation:** ```cpp TEST_CASE("TI_TYPES_001_MCPToolToJson", "[mcp][types]") { MCPTool tool; tool.name = "read_file"; tool.description = "Read a file"; tool.inputSchema = {{"type", "object"}, {"properties", {{"path", {{"type", "string"}}}}}}; json j = tool.toJson(); REQUIRE(j["name"] == "read_file"); REQUIRE(j["description"] == "Read a file"); REQUIRE(j["inputSchema"]["type"] == "object"); } TEST_CASE("TI_TYPES_011_JsonRpcResponseIsError", "[mcp][types]") { json errorJson = { {"jsonrpc", "2.0"}, {"id", 1}, {"error", {{"code", -32600}, {"message", "Invalid Request"}}} }; auto response = JsonRpcResponse::fromJson(errorJson); REQUIRE(response.isError() == true); REQUIRE(response.error.value()["code"] == -32600); } ``` --- ### 3.2 StdioTransport (20 TI) | # | Test | Description | |---|------|-------------| | 1 | `TI_TRANSPORT_001_StartSpawnsProcess` | `start()` lance le processus enfant | | 2 | `TI_TRANSPORT_002_StartFailsInvalidCommand` | `start()` retourne false si commande invalide | | 3 | `TI_TRANSPORT_003_StopKillsProcess` | `stop()` termine le processus | | 4 | `TI_TRANSPORT_004_IsRunningReflectsState` | `isRunning()` reflete l'etat reel | | 5 | `TI_TRANSPORT_005_SendRequestWritesToStdin` | Request serialisee vers stdin | | 6 | `TI_TRANSPORT_006_SendRequestReadsResponse` | Response lue depuis stdout | | 7 | `TI_TRANSPORT_007_SendRequestTimeout` | Timeout si pas de reponse | | 8 | `TI_TRANSPORT_008_SendRequestIdMatching` | Response matchee par ID | | 9 | `TI_TRANSPORT_009_ConcurrentRequests` | Multiple requests simultanées OK | | 10 | `TI_TRANSPORT_010_SendNotificationNoResponse` | Notification n'attend pas de reponse | | 11 | `TI_TRANSPORT_011_ReaderThreadStartsOnStart` | Thread reader demarre avec `start()` | | 12 | `TI_TRANSPORT_012_ReaderThreadStopsOnStop` | Thread reader s'arrete avec `stop()` | | 13 | `TI_TRANSPORT_013_JsonParseErrorHandled` | JSON invalide n'crash pas | | 14 | `TI_TRANSPORT_014_ProcessCrashDetected` | Crash process detecte rapidement | | 15 | `TI_TRANSPORT_015_LargeMessageHandling` | Messages > 64KB transmis | | 16 | `TI_TRANSPORT_016_MultilineJsonHandling` | JSON multiline traite correctement | | 17 | `TI_TRANSPORT_017_EnvVariablesPassedToProcess` | Variables env transmises au processus | | 18 | `TI_TRANSPORT_018_ArgsPassedToProcess` | Arguments CLI transmis | | 19 | `TI_TRANSPORT_019_DestructorCleansUp` | Destructeur nettoie ressources | | 20 | `TI_TRANSPORT_020_RestartAfterStop` | `start()` apres `stop()` fonctionne | **Implementation (avec mock process):** ```cpp // Pour les tests, on peut creer un mini-serveur echo en Python ou utiliser un mock TEST_CASE("TI_TRANSPORT_007_SendRequestTimeout", "[mcp][transport]") { MCPServerConfig config; config.command = "cat"; // cat ne repond jamais au JSON-RPC StdioTransport transport(config); transport.start(); JsonRpcRequest request; request.id = 1; request.method = "test"; auto response = transport.sendRequest(request, 100); // 100ms timeout REQUIRE(response.isError() == true); transport.stop(); } // Test avec echo server (Python script) TEST_CASE("TI_TRANSPORT_006_SendRequestReadsResponse", "[mcp][transport]") { MCPServerConfig config; config.command = "python"; config.args = {"tests/fixtures/echo_server.py"}; StdioTransport transport(config); REQUIRE(transport.start() == true); JsonRpcRequest request; request.id = 42; request.method = "echo"; request.params = {{"message", "hello"}}; auto response = transport.sendRequest(request, 5000); REQUIRE(response.isError() == false); REQUIRE(response.id == 42); transport.stop(); } ``` --- ### 3.3 MCPClient (15 TI) | # | Test | Description | |---|------|-------------| | 1 | `TI_CLIENT_001_LoadConfigValid` | `loadConfig()` parse mcp.json valide | | 2 | `TI_CLIENT_002_LoadConfigInvalid` | `loadConfig()` gere fichier invalide | | 3 | `TI_CLIENT_003_LoadConfigMissingFile` | `loadConfig()` retourne false si fichier absent | | 4 | `TI_CLIENT_004_ConnectAllStartsServers` | `connectAll()` demarre tous les serveurs enabled | | 5 | `TI_CLIENT_005_ConnectAllSkipsDisabled` | `connectAll()` skip les serveurs `enabled: false` | | 6 | `TI_CLIENT_006_ConnectSingleServer` | `connect(name)` demarre un seul serveur | | 7 | `TI_CLIENT_007_DisconnectSingleServer` | `disconnect(name)` arrete un serveur | | 8 | `TI_CLIENT_008_DisconnectAllCleansUp` | `disconnectAll()` arrete tous les serveurs | | 9 | `TI_CLIENT_009_ListAllToolsAggregates` | `listAllTools()` combine tools de tous serveurs | | 10 | `TI_CLIENT_010_ToolNamePrefixed` | Tools prefixes par nom serveur (`server:tool`) | | 11 | `TI_CLIENT_011_CallToolRoutesToServer` | `callTool()` route vers bon serveur | | 12 | `TI_CLIENT_012_CallToolInvalidName` | `callTool()` avec nom invalide retourne erreur | | 13 | `TI_CLIENT_013_CallToolDisconnectedServer` | `callTool()` sur serveur deconnecte retourne erreur | | 14 | `TI_CLIENT_014_ToolCountAccurate` | `toolCount()` reflete nombre total de tools | | 15 | `TI_CLIENT_015_IsConnectedAccurate` | `isConnected(name)` reflete etat reel | **Implementation:** ```cpp TEST_CASE("TI_CLIENT_010_ToolNamePrefixed", "[mcp][client]") { MCPClient client; // Utiliser mock transport client.loadConfig("tests/fixtures/mock_mcp.json"); // Le mock simule un serveur avec tool "read_file" client.connectAll(); auto tools = client.listAllTools(); bool hasPrefix = false; for (const auto& tool : tools) { if (tool.name.find(":") != std::string::npos) { hasPrefix = true; break; } } REQUIRE(hasPrefix == true); client.disconnectAll(); } TEST_CASE("TI_CLIENT_012_CallToolInvalidName", "[mcp][client]") { MCPClient client; client.loadConfig("tests/fixtures/mock_mcp.json"); client.connectAll(); auto result = client.callTool("nonexistent:tool", {}); REQUIRE(result.isError == true); client.disconnectAll(); } ``` --- ## Phase 4: Fixtures et Scripts de Test ### 4.1 Echo Server MCP (Python) `tests/fixtures/echo_server.py` ```python #!/usr/bin/env python3 import json import sys def main(): while True: line = sys.stdin.readline() if not line: break try: request = json.loads(line) response = { "jsonrpc": "2.0", "id": request.get("id"), "result": request.get("params", {}) } sys.stdout.write(json.dumps(response) + "\n") sys.stdout.flush() except: pass if __name__ == "__main__": main() ``` ### 4.2 Mock MCP Server (pour tests d'integration) `tests/fixtures/mock_mcp_server.py` ```python #!/usr/bin/env python3 import json import sys TOOLS = [ {"name": "test_tool", "description": "A test tool", "inputSchema": {"type": "object"}} ] def handle_request(request): method = request.get("method") if method == "initialize": return { "protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": "MockServer", "version": "1.0"} } elif method == "tools/list": return {"tools": TOOLS} elif method == "tools/call": return {"content": [{"type": "text", "text": "Tool executed"}]} return {"error": {"code": -32601, "message": "Method not found"}} def main(): while True: line = sys.stdin.readline() if not line: break try: request = json.loads(line) result = handle_request(request) response = {"jsonrpc": "2.0", "id": request.get("id")} if "error" in result: response["error"] = result["error"] else: response["result"] = result sys.stdout.write(json.dumps(response) + "\n") sys.stdout.flush() except Exception as e: pass if __name__ == "__main__": main() ``` ### 4.3 Config Mock MCP `tests/fixtures/mock_mcp.json` ```json { "mock_server": { "command": "python", "args": ["tests/fixtures/mock_mcp_server.py"], "enabled": true }, "disabled_server": { "command": "nonexistent", "enabled": false } } ``` --- ## Phase 5: CMakeLists.txt Tests ```cmake # ============================================================================ # Tests d'Integration # ============================================================================ # Fetch Catch2 if not already available FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.4.0 ) FetchContent_MakeAvailable(Catch2) # Test executable add_executable(aissia_tests tests/main.cpp # Mocks tests/mocks/MockIO.cpp # Module tests tests/modules/SchedulerModuleTests.cpp tests/modules/NotificationModuleTests.cpp tests/modules/MonitoringModuleTests.cpp tests/modules/AIModuleTests.cpp tests/modules/VoiceModuleTests.cpp tests/modules/StorageModuleTests.cpp # MCP tests tests/mcp/MCPTypesTests.cpp tests/mcp/StdioTransportTests.cpp tests/mcp/MCPClientTests.cpp ) target_link_libraries(aissia_tests PRIVATE Catch2::Catch2WithMain GroveEngine::impl AissiaTools spdlog::spdlog ) target_include_directories(aissia_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/tests ) # Copy test fixtures file(COPY tests/fixtures/ DESTINATION ${CMAKE_BINARY_DIR}/tests/fixtures) # CTest integration include(CTest) include(Catch) catch_discover_tests(aissia_tests) # Custom target for running tests add_custom_target(test_all COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure DEPENDS aissia_tests COMMENT "Running all integration tests" ) ``` --- ## Ordre d'Implementation Recommande ### Sprint 1: Infrastructure (2-3 jours) 1. Creer structure `tests/` 2. Implementer `MockIO.hpp` 3. Implementer `MockTransport.hpp` 4. Implementer `TimeSimulator.hpp` 5. Setup CMakeLists.txt tests 6. Creer fixtures Python ### Sprint 2: Tests MCPTypes (1 jour) 1. `TI_TYPES_001` a `TI_TYPES_015` 2. Valider serialisation/deserialisation ### Sprint 3: Tests StdioTransport (2 jours) 1. `TI_TRANSPORT_001` a `TI_TRANSPORT_010` (basic) 2. `TI_TRANSPORT_011` a `TI_TRANSPORT_020` (advanced) ### Sprint 4: Tests MCPClient (1 jour) 1. `TI_CLIENT_001` a `TI_CLIENT_015` ### Sprint 5: Tests Modules (3-4 jours) 1. SchedulerModule (10 TI) 2. NotificationModule (10 TI) 3. MonitoringModule (10 TI) 4. AIModule (10 TI) 5. VoiceModule (10 TI) 6. StorageModule (10 TI) --- ## Metriques de Succes - [ ] 110 tests implementes - [ ] Couverture > 80% pour chaque module - [ ] Tous les tests passent en CI - [ ] Temps d'execution < 30 secondes - [ ] Aucune dependance externe (sauf Python pour mock MCP) --- ## Commandes de Build et Execution ```bash # Build complet avec tests cmake -B build -DBUILD_TESTING=ON cmake --build build # Executer tous les tests cmake --build build --target test_all # Executer tests specifiques ./build/aissia_tests "[scheduler]" # Tests scheduler ./build/aissia_tests "[mcp]" # Tests MCP ./build/aissia_tests "[integration]" # Tous les TI # Avec verbose ./build/aissia_tests -s -d yes ```