27 KiB
27 KiB
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
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
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
class TimeSimulator {
public:
float m_gameTime = 0.0f;
json createInput(float deltaTime = 0.1f);
void advance(float seconds);
void setTime(float time);
};
MessageCapture.hpp
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:
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:
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:
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:
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:
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:
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):
// 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:
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
#!/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
#!/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
{
"mock_server": {
"command": "python",
"args": ["tests/fixtures/mock_mcp_server.py"],
"enabled": true
},
"disabled_server": {
"command": "nonexistent",
"enabled": false
}
}
Phase 5: CMakeLists.txt Tests
# ============================================================================
# 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)
- Creer structure
tests/ - Implementer
MockIO.hpp - Implementer
MockTransport.hpp - Implementer
TimeSimulator.hpp - Setup CMakeLists.txt tests
- Creer fixtures Python
Sprint 2: Tests MCPTypes (1 jour)
TI_TYPES_001aTI_TYPES_015- Valider serialisation/deserialisation
Sprint 3: Tests StdioTransport (2 jours)
TI_TRANSPORT_001aTI_TRANSPORT_010(basic)TI_TRANSPORT_011aTI_TRANSPORT_020(advanced)
Sprint 4: Tests MCPClient (1 jour)
TI_CLIENT_001aTI_CLIENT_015
Sprint 5: Tests Modules (3-4 jours)
- SchedulerModule (10 TI)
- NotificationModule (10 TI)
- MonitoringModule (10 TI)
- AIModule (10 TI)
- VoiceModule (10 TI)
- 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
# 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