# Plan d'Implementation - Tests d'Integration CELUNA

## 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 celuna::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(celuna_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(celuna_tests PRIVATE
    Catch2::Catch2WithMain
    GroveEngine::impl
    CelunaTools
    spdlog::spdlog
)

target_include_directories(celuna_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(celuna_tests)

# Custom target for running tests
add_custom_target(test_all
    COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
    DEPENDS celuna_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/celuna_tests "[scheduler]"      # Tests scheduler
./build/celuna_tests "[mcp]"            # Tests MCP
./build/celuna_tests "[integration]"    # Tous les TI

# Avec verbose
./build/celuna_tests -s -d yes
```
