259 lines
7.9 KiB
C++
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
|
|
}
|