aissia/PLAN_TESTS_INTEGRATION.md

734 lines
27 KiB
Markdown

# 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<std::pair<std::string, json>> publishedMessages;
// Queue de messages a recevoir
std::queue<grove::Message> 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<JsonRpcRequest> sentRequests;
std::queue<JsonRpcResponse> 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
```