| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- # -*- coding: utf-8 -*-
- """
- Tests für Conversation Module.
- """
- import pytest
- import asyncio
- from datetime import datetime, timedelta
- from unittest.mock import MagicMock, AsyncMock, patch
- from trixy_core.conversation.session import (
- ConversationSession,
- SessionConfig,
- SessionState,
- SessionTurn,
- TurnType,
- )
- from trixy_core.conversation.context import (
- ConversationContext,
- ContextVariable,
- ContextScope,
- )
- class TestSessionConfig:
- """Tests für SessionConfig."""
- def test_default_config(self):
- """Testet Standard-Konfiguration."""
- config = SessionConfig()
- assert config.input_timeout_seconds == 60.0
- assert config.follow_up_timeout_seconds == 60.0
- assert config.max_turns == 20
- assert config.max_follow_ups == 5
- class TestSessionState:
- """Tests für SessionState."""
- def test_states(self):
- """Testet Session-Zustände."""
- assert SessionState.CREATED.value == "created"
- assert SessionState.AWAITING_INPUT.value == "awaiting"
- assert SessionState.PROCESSING.value == "processing"
- assert SessionState.FOLLOW_UP.value == "follow_up"
- assert SessionState.COMPLETED.value == "completed"
- class TestTurnType:
- """Tests für TurnType."""
- def test_types(self):
- """Testet Turn-Typen."""
- assert TurnType.USER_SPEECH.value == "user_speech"
- assert TurnType.USER_TEXT.value == "user_text"
- assert TurnType.ASSISTANT_SPEECH.value == "assistant_speech"
- assert TurnType.FOLLOW_UP.value == "follow_up"
- class TestSessionTurn:
- """Tests für SessionTurn."""
- def test_turn_creation(self):
- """Testet Turn-Erstellung."""
- turn = SessionTurn(
- turn_id="t1",
- turn_type=TurnType.USER_SPEECH,
- timestamp=datetime.now(),
- content="Hallo Trixy",
- confidence=0.95,
- )
- assert turn.content == "Hallo Trixy"
- assert turn.confidence == 0.95
- def test_turn_to_dict(self):
- """Testet Dictionary-Konvertierung."""
- turn = SessionTurn(
- turn_id="t1",
- turn_type=TurnType.ASSISTANT_SPEECH,
- timestamp=datetime.now(),
- content="Hallo!",
- intent="greeting",
- )
- d = turn.to_dict()
- assert d["turn_id"] == "t1"
- assert d["turn_type"] == "assistant_speech"
- assert d["intent"] == "greeting"
- class TestConversationSession:
- """Tests für ConversationSession."""
- @pytest.fixture
- def config(self):
- """Erstellt Test-Konfiguration."""
- return SessionConfig(
- input_timeout_seconds=10.0,
- max_turns=5,
- max_follow_ups=2,
- )
- @pytest.fixture
- def session(self, config):
- """Erstellt Session."""
- return ConversationSession(
- session_id="test-123",
- satellite_id="sat-001",
- wakeword_type="custom",
- config=config,
- )
- def test_session_creation(self, session):
- """Testet Session-Erstellung."""
- assert session.session_id == "test-123"
- assert session.satellite_id == "sat-001"
- assert session.state == SessionState.CREATED
- assert not session.is_completed
- def test_session_start(self, session):
- """Testet Session-Start."""
- session.start()
- assert session.state == SessionState.AWAITING_INPUT
- assert session.started_at is not None
- def test_add_user_turn(self, session):
- """Testet User-Turn hinzufügen."""
- turn = session.add_user_turn(
- content="Wie ist das Wetter?",
- intent="weather_query",
- )
- assert session.turn_count == 1
- assert turn.content == "Wie ist das Wetter?"
- assert session.state == SessionState.PROCESSING
- def test_add_assistant_turn(self, session):
- """Testet Assistenten-Turn hinzufügen."""
- turn = session.add_assistant_turn(
- content="Das Wetter ist schön.",
- )
- assert session.turn_count == 1
- assert session.state == SessionState.RESPONDING
- def test_add_follow_up(self, session):
- """Testet Rückfrage hinzufügen."""
- session.add_user_turn("Mach das Licht an")
- turn = session.add_assistant_turn(
- content="Welches Licht meinst du?",
- is_follow_up=True,
- )
- assert turn.requires_response is True
- assert session.follow_up_count == 1
- assert session.has_pending_follow_up
- assert session.state == SessionState.FOLLOW_UP
- def test_answer_follow_up(self, session):
- """Testet Antwort auf Rückfrage."""
- session.add_user_turn("Mach das Licht an")
- follow_up = session.add_assistant_turn(
- content="Welches Licht?",
- is_follow_up=True,
- )
- answer = session.add_user_turn("Das Wohnzimmerlicht")
- assert follow_up.response_turn_id == answer.turn_id
- assert not session.has_pending_follow_up
- def test_complete_session(self, session):
- """Testet Session-Abschluss."""
- session.start()
- session.complete("Test abgeschlossen")
- assert session.is_completed
- assert session.state == SessionState.COMPLETED
- assert session.completed_at is not None
- def test_timeout_session(self, session):
- """Testet Session-Timeout."""
- session.timeout()
- assert session.is_completed
- assert session.state == SessionState.TIMEOUT
- def test_cancel_session(self, session):
- """Testet Session-Abbruch."""
- session.cancel("Abgebrochen")
- assert session.is_completed
- assert session.state == SessionState.CANCELLED
- def test_user_turns(self, session):
- """Testet User-Turns-Filter."""
- session.add_user_turn("User 1")
- session.add_assistant_turn("Assistent 1")
- session.add_user_turn("User 2")
- user_turns = session.user_turns
- assert len(user_turns) == 2
- def test_assistant_turns(self, session):
- """Testet Assistenten-Turns-Filter."""
- session.add_user_turn("User")
- session.add_assistant_turn("Antwort")
- session.add_assistant_turn("Rückfrage", is_follow_up=True)
- assistant_turns = session.assistant_turns
- assert len(assistant_turns) == 2
- def test_max_turns_limit(self, session):
- """Testet Turn-Limit."""
- for i in range(5):
- session.add_user_turn(f"Turn {i}")
- with pytest.raises(RuntimeError):
- session.add_user_turn("Zu viel")
- def test_context(self, session):
- """Testet Kontext."""
- session.set_context("room", "wohnzimmer")
- assert session.get_context("room") == "wohnzimmer"
- assert session.get_context("missing", "default") == "default"
- def test_to_dict(self, session):
- """Testet Dictionary-Konvertierung."""
- session.add_user_turn("Test")
- d = session.to_dict()
- assert d["session_id"] == "test-123"
- assert d["turn_count"] == 1
- assert "turns" in d
- def test_transcript(self, session):
- """Testet Transkript."""
- session.add_user_turn("Hallo")
- session.add_assistant_turn("Hallo!")
- session.add_assistant_turn("Wie kann ich helfen?", is_follow_up=True)
- transcript = session.get_transcript()
- assert "User: Hallo" in transcript
- assert "Assistent: Hallo!" in transcript
- assert "Rückfrage" in transcript
- def test_duration(self, session):
- """Testet Dauer-Berechnung."""
- assert session.duration_seconds >= 0
- session.complete()
- assert session.duration_seconds >= 0
- def test_is_active(self, session):
- """Testet is_active."""
- assert session.is_active
- session.complete()
- assert not session.is_active
- class TestContextScope:
- """Tests für ContextScope."""
- def test_scopes(self):
- """Testet Kontext-Scopes."""
- assert ContextScope.TURN.value == "turn"
- assert ContextScope.SESSION.value == "session"
- assert ContextScope.USER.value == "user"
- assert ContextScope.GLOBAL.value == "global"
- class TestContextVariable:
- """Tests für ContextVariable."""
- def test_variable_creation(self):
- """Testet Variable-Erstellung."""
- var = ContextVariable(
- name="test",
- value=42,
- scope=ContextScope.SESSION,
- )
- assert var.name == "test"
- assert var.value == 42
- def test_update(self):
- """Testet Update."""
- var = ContextVariable(name="test", value=1)
- old_time = var.updated_at
- var.update(2)
- assert var.value == 2
- assert var.updated_at >= old_time
- class TestConversationContext:
- """Tests für ConversationContext."""
- @pytest.fixture
- def context(self):
- """Erstellt Context."""
- return ConversationContext()
- def test_set_get(self, context):
- """Testet Set/Get."""
- context.set("test", "value")
- assert context.get("test") == "value"
- def test_get_default(self, context):
- """Testet Default-Wert."""
- assert context.get("missing", "default") == "default"
- def test_has(self, context):
- """Testet has."""
- context.set("exists", True)
- assert context.has("exists")
- assert not context.has("missing")
- def test_remove(self, context):
- """Testet remove."""
- context.set("test", "value")
- assert context.remove("test")
- assert not context.has("test")
- def test_turn_scope(self, context):
- """Testet Turn-Scope."""
- context.set("turn_var", "value", ContextScope.TURN)
- assert context.get("turn_var") == "value"
- context.clear_turn()
- assert not context.has("turn_var")
- def test_turn_overrides_session(self, context):
- """Testet Turn überschreibt Session."""
- context.set("var", "session", ContextScope.SESSION)
- context.set("var", "turn", ContextScope.TURN)
- assert context.get("var") == "turn"
- def test_clear_scope(self, context):
- """Testet Scope-Löschen."""
- context.set("a", 1, ContextScope.SESSION)
- context.set("b", 2, ContextScope.USER)
- context.clear_scope(ContextScope.SESSION)
- assert not context.has("a")
- assert context.has("b")
- def test_get_all(self, context):
- """Testet get_all."""
- context.set("a", 1, ContextScope.SESSION)
- context.set("b", 2, ContextScope.TURN)
- all_vars = context.get_all()
- assert all_vars["a"] == 1
- assert all_vars["b"] == 2
- def test_merge(self, context):
- """Testet merge."""
- other = ConversationContext()
- other.set("other_var", "value")
- context.merge(other)
- assert context.get("other_var") == "value"
- def test_intent_helpers(self, context):
- """Testet Intent-Helper."""
- context.set_intent("weather_query", 0.95)
- assert context.get_intent() == "weather_query"
- def test_entity_helpers(self, context):
- """Testet Entity-Helper."""
- context.set_entities({"location": "Berlin"})
- entities = context.get_entities()
- assert entities["location"] == "Berlin"
- def test_slot_helpers(self, context):
- """Testet Slot-Helper."""
- context.set_slot("room", "wohnzimmer")
- context.set_slot("device", "licht")
- assert context.get_slot("room") == "wohnzimmer"
- assert context.slots == {"room": "wohnzimmer", "device": "licht"}
- context.clear_slots()
- assert context.slots == {}
- def test_to_dict(self, context):
- """Testet Dictionary-Konvertierung."""
- context.set("test", "value")
- d = context.to_dict()
- assert "variables" in d
- assert "turn_variables" in d
|