aissia/tests/modules/VoiceModuleTests.cpp

259 lines
7.9 KiB
C++

/**
* @file VoiceModuleTests.cpp
* @brief Integration tests for VoiceModule (10 TI)
*/
#include <catch2/catch_test_macros.hpp>
#include "mocks/MockIO.hpp"
#include "utils/TimeSimulator.hpp"
#include "utils/TestHelpers.hpp"
#include "modules/VoiceModule.h"
#include <grove/JsonDataNode.h>
using namespace aissia;
using namespace aissia::tests;
// ============================================================================
// Test Fixture
// ============================================================================
class VoiceTestFixture {
public:
MockIO io;
TimeSimulator time;
VoiceModule module;
void configure(const json& config = json::object()) {
json fullConfig = {
{"ttsEnabled", true},
{"sttEnabled", true},
{"language", "fr"}
};
fullConfig.merge_patch(config);
grove::JsonDataNode configNode("config", fullConfig);
module.setConfiguration(configNode, &io, nullptr);
}
void process() {
grove::JsonDataNode input("input", time.createInput());
module.process(input);
}
};
// ============================================================================
// TI_VOICE_001: AI Response Triggers Speak
// ============================================================================
TEST_CASE("TI_VOICE_001_AIResponseTriggersSpeak", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Receive AI response
f.io.injectMessage("ai:response", {
{"text", "Voici la reponse a ta question"}
});
f.process();
// Verify speak request
REQUIRE(f.io.wasPublished("voice:speak"));
auto msg = f.io.getLastPublished("voice:speak");
REQUIRE(msg["text"] == "Voici la reponse a ta question");
}
// ============================================================================
// TI_VOICE_002: Suggestion Priority Speak
// ============================================================================
TEST_CASE("TI_VOICE_002_SuggestionPrioritySpeak", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Receive suggestion (should be priority)
f.io.injectMessage("ai:suggestion", {
{"message", "Tu devrais faire une pause"},
{"duration", 5}
});
f.process();
// Verify speak with priority
REQUIRE(f.io.wasPublished("voice:speak"));
auto msg = f.io.getLastPublished("voice:speak");
REQUIRE(msg["priority"] == true);
}
// ============================================================================
// TI_VOICE_003: Speaking Started Updates State
// ============================================================================
TEST_CASE("TI_VOICE_003_SpeakingStartedUpdatesState", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Initially idle
REQUIRE(f.module.isIdle() == true);
// Receive speaking started
f.io.injectMessage("voice:speaking_started", {{"text", "Hello"}});
f.process();
// Should be speaking
REQUIRE(f.module.isIdle() == false);
}
// ============================================================================
// TI_VOICE_004: Speaking Ended Updates State
// ============================================================================
TEST_CASE("TI_VOICE_004_SpeakingEndedUpdatesState", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Start speaking
f.io.injectMessage("voice:speaking_started", {{"text", "Hello"}});
f.process();
REQUIRE(f.module.isIdle() == false);
// End speaking
f.io.injectMessage("voice:speaking_ended", {});
f.process();
// Should be idle
REQUIRE(f.module.isIdle() == true);
}
// ============================================================================
// TI_VOICE_005: IsIdle Reflects Speaking
// ============================================================================
TEST_CASE("TI_VOICE_005_IsIdleReflectsSpeaking", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Not speaking = idle
REQUIRE(f.module.isIdle() == true);
// Start speaking
f.io.injectMessage("voice:speaking_started", {});
f.process();
REQUIRE(f.module.isIdle() == false);
// Stop speaking
f.io.injectMessage("voice:speaking_ended", {});
f.process();
REQUIRE(f.module.isIdle() == true);
}
// ============================================================================
// TI_VOICE_006: Transcription Forwarded (No Re-publish)
// ============================================================================
TEST_CASE("TI_VOICE_006_TranscriptionForwarded", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Receive transcription
f.io.injectMessage("voice:transcription", {
{"text", "Test transcription"},
{"confidence", 0.9}
});
f.process();
// VoiceModule should NOT re-publish transcription
// It just updates internal state
REQUIRE(f.io.countPublished("voice:transcription") == 0);
}
// ============================================================================
// TI_VOICE_007: Total Spoken Incremented
// ============================================================================
TEST_CASE("TI_VOICE_007_TotalSpokenIncremented", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Complete one speak cycle
f.io.injectMessage("voice:speaking_started", {});
f.process();
f.io.injectMessage("voice:speaking_ended", {});
f.process();
// Complete another
f.io.injectMessage("voice:speaking_started", {});
f.process();
f.io.injectMessage("voice:speaking_ended", {});
f.process();
// Verify counter
auto state = f.module.getState();
// TODO: Verify totalSpoken == 2
SUCCEED(); // Placeholder
}
// ============================================================================
// TI_VOICE_008: TTS Disabled Config
// ============================================================================
TEST_CASE("TI_VOICE_008_TTSDisabledConfig", "[voice][integration]") {
VoiceTestFixture f;
f.configure({{"ttsEnabled", false}});
// Try to trigger speak
f.io.injectMessage("ai:response", {{"text", "Should not speak"}});
f.process();
// Should NOT publish speak request
REQUIRE(f.io.wasPublished("voice:speak") == false);
}
// ============================================================================
// TI_VOICE_009: Tool Command Speak
// ============================================================================
TEST_CASE("TI_VOICE_009_ToolCommandSpeak", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Send speak command via tool
f.io.injectMessage("voice:command", {
{"action", "speak"},
{"text", "Hello from tool"}
});
f.process();
// Verify speak published
REQUIRE(f.io.wasPublished("voice:speak"));
auto msg = f.io.getLastPublished("voice:speak");
REQUIRE(msg["text"] == "Hello from tool");
}
// ============================================================================
// TI_VOICE_010: State Serialization
// ============================================================================
TEST_CASE("TI_VOICE_010_StateSerialization", "[voice][integration]") {
VoiceTestFixture f;
f.configure();
// Build state
f.io.injectMessage("voice:speaking_started", {});
f.process();
f.io.injectMessage("voice:speaking_ended", {});
f.process();
// Get state
auto state = f.module.getState();
REQUIRE(state != nullptr);
// Restore
VoiceModule module2;
grove::JsonDataNode configNode2("config", json::object());
module2.setConfiguration(configNode2, &f.io, nullptr);
module2.setState(*state);
auto state2 = module2.getState();
REQUIRE(state2 != nullptr);
SUCCEED(); // Placeholder
}