| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846 |
- # -*- coding: utf-8 -*-
- """
- Tests für das Arbitration-Modul.
- Testet Kandidaten, Zeitfenster, Strategien und den Arbitrator-Service.
- """
- import pytest
- import asyncio
- from datetime import datetime, timedelta
- from trixy_core.arbitration import (
- # Candidate
- ArbitrationCandidate,
- CandidateState,
- # Window
- ArbitrationWindow,
- WindowConfig,
- WindowState,
- # Strategies
- ArbitrationStrategy,
- LoudestStrategy,
- ConfidenceStrategy,
- CombinedStrategy,
- ProximityStrategy,
- # Arbitrator
- WakewordArbitrator,
- ArbitrationConfig,
- ArbitrationResult,
- )
- from trixy_core.arbitration.strategies.combined import CombinedWeights
- from trixy_core.arbitration.strategies.proximity import ProximityWeights, RoomRelevance
- # =============================================================================
- # ArbitrationCandidate Tests
- # =============================================================================
- class TestArbitrationCandidate:
- """Tests für ArbitrationCandidate."""
- def test_create_candidate(self):
- """Test Kandidaten-Erstellung."""
- candidate = ArbitrationCandidate(
- satellite_id="living_room",
- room_id="wohnzimmer",
- alias="Wohnzimmer",
- audio_level=0.85,
- confidence=0.92,
- )
- assert candidate.satellite_id == "living_room"
- assert candidate.room_id == "wohnzimmer"
- assert candidate.audio_level == 0.85
- assert candidate.confidence == 0.92
- assert candidate.state == CandidateState.PENDING
- def test_candidate_requires_satellite_id(self):
- """Test dass satellite_id erforderlich ist."""
- with pytest.raises(ValueError):
- ArbitrationCandidate(satellite_id="")
- def test_audio_level_normalization(self):
- """Test dass audio_level auf 0-1 normalisiert wird."""
- candidate = ArbitrationCandidate(
- satellite_id="test",
- audio_level=1.5, # Über 1.0
- )
- assert candidate.audio_level == 1.0
- candidate2 = ArbitrationCandidate(
- satellite_id="test",
- audio_level=-0.5, # Unter 0.0
- )
- assert candidate2.audio_level == 0.0
- def test_candidate_state_transitions(self):
- """Test Zustandsübergänge."""
- candidate = ArbitrationCandidate(satellite_id="test")
- assert candidate.is_pending
- assert not candidate.is_selected
- assert not candidate.is_rejected
- candidate.select()
- assert candidate.is_selected
- assert not candidate.is_pending
- candidate2 = ArbitrationCandidate(satellite_id="test2")
- candidate2.reject(reason="Anderer gewählt")
- assert candidate2.is_rejected
- assert candidate2.metadata.get("reject_reason") == "Anderer gewählt"
- def test_candidate_age(self):
- """Test Kandidaten-Alter."""
- candidate = ArbitrationCandidate(satellite_id="test")
- assert candidate.age_ms >= 0
- def test_candidate_to_dict(self):
- """Test Dictionary-Konvertierung."""
- candidate = ArbitrationCandidate(
- satellite_id="test",
- room_id="room1",
- audio_level=0.75,
- confidence=0.9,
- )
- data = candidate.to_dict()
- assert data["satellite_id"] == "test"
- assert data["room_id"] == "room1"
- assert data["audio_level"] == 0.75
- assert data["confidence"] == 0.9
- # =============================================================================
- # ArbitrationWindow Tests
- # =============================================================================
- class TestArbitrationWindow:
- """Tests für ArbitrationWindow."""
- @pytest.mark.asyncio
- async def test_window_open_close(self):
- """Test Fenster öffnen und schließen."""
- window = ArbitrationWindow()
- assert window.is_closed
- assert not window.is_open
- await window.open()
- assert window.is_open
- assert not window.is_closed
- candidates = await window.close(reason="test")
- assert len(candidates) == 0
- @pytest.mark.asyncio
- async def test_window_add_candidate(self):
- """Test Kandidaten hinzufügen."""
- window = ArbitrationWindow()
- await window.open()
- candidate = ArbitrationCandidate(
- satellite_id="test",
- audio_level=0.8,
- )
- success = await window.add_candidate(candidate)
- assert success
- assert window.candidate_count == 1
- # Nochmal denselben Satellite - sollte aktualisieren
- candidate2 = ArbitrationCandidate(
- satellite_id="test",
- audio_level=0.9, # Höher
- )
- success = await window.add_candidate(candidate2)
- assert success
- assert window.candidate_count == 1 # Immer noch 1
- # Der audio_level sollte aktualisiert sein
- stored = window.get_candidate("test")
- assert stored.audio_level == 0.9
- await window.reset()
- @pytest.mark.asyncio
- async def test_window_cannot_add_when_closed(self):
- """Test dass Kandidaten nicht bei geschlossenem Fenster hinzugefügt werden können."""
- window = ArbitrationWindow()
- candidate = ArbitrationCandidate(satellite_id="test")
- success = await window.add_candidate(candidate)
- assert not success
- assert window.candidate_count == 0
- @pytest.mark.asyncio
- async def test_window_max_candidates(self):
- """Test maximale Kandidaten-Anzahl."""
- config = WindowConfig(max_candidates=2)
- window = ArbitrationWindow(config=config)
- await window.open()
- await window.add_candidate(ArbitrationCandidate(satellite_id="s1"))
- await window.add_candidate(ArbitrationCandidate(satellite_id="s2"))
- # Dritter sollte abgelehnt werden
- success = await window.add_candidate(
- ArbitrationCandidate(satellite_id="s3")
- )
- assert not success
- assert window.candidate_count == 2
- await window.reset()
- @pytest.mark.asyncio
- async def test_window_has_candidate(self):
- """Test has_candidate Methode."""
- window = ArbitrationWindow()
- await window.open()
- await window.add_candidate(ArbitrationCandidate(satellite_id="test"))
- assert window.has_candidate("test")
- assert not window.has_candidate("other")
- await window.reset()
- @pytest.mark.asyncio
- async def test_window_duration(self):
- """Test Fenster-Dauer."""
- window = ArbitrationWindow()
- await window.open()
- # Kurz warten
- await asyncio.sleep(0.05)
- assert window.duration_ms >= 50
- assert window.remaining_ms < window.config.window_duration_ms
- await window.reset()
- @pytest.mark.asyncio
- async def test_window_auto_close(self):
- """Test automatisches Schließen nach Timeout."""
- callback_called = []
- async def on_complete(candidates):
- callback_called.append(len(candidates))
- config = WindowConfig(
- window_duration_ms=100,
- min_wait_ms=50,
- )
- window = ArbitrationWindow(config=config, on_complete=on_complete)
- await window.open()
- await window.add_candidate(ArbitrationCandidate(satellite_id="test"))
- # Warten auf auto-close
- await asyncio.sleep(0.15)
- assert len(callback_called) == 1
- assert callback_called[0] == 1
- @pytest.mark.asyncio
- async def test_window_reset(self):
- """Test Fenster zurücksetzen."""
- window = ArbitrationWindow()
- await window.open()
- await window.add_candidate(ArbitrationCandidate(satellite_id="test"))
- await window.reset()
- assert window.is_closed
- assert window.candidate_count == 0
- # =============================================================================
- # Strategy Tests
- # =============================================================================
- class TestLoudestStrategy:
- """Tests für LoudestStrategy."""
- def test_select_loudest(self):
- """Test dass lautester Kandidat gewählt wird."""
- strategy = LoudestStrategy()
- candidates = [
- ArbitrationCandidate(satellite_id="quiet", audio_level=0.5),
- ArbitrationCandidate(satellite_id="loud", audio_level=0.9),
- ArbitrationCandidate(satellite_id="medium", audio_level=0.7),
- ]
- winner = strategy.select(candidates)
- assert winner is not None
- assert winner.satellite_id == "loud"
- assert winner.score == 0.9
- def test_select_empty_list(self):
- """Test mit leerer Liste."""
- strategy = LoudestStrategy()
- winner = strategy.select([])
- assert winner is None
- def test_min_audio_level(self):
- """Test Minimum Audio-Level Filter."""
- strategy = LoudestStrategy(min_audio_level=0.5)
- candidates = [
- ArbitrationCandidate(satellite_id="too_quiet", audio_level=0.3),
- ArbitrationCandidate(satellite_id="ok", audio_level=0.6),
- ]
- winner = strategy.select(candidates)
- assert winner is not None
- assert winner.satellite_id == "ok"
- def test_all_below_minimum(self):
- """Test wenn alle unter Minimum."""
- strategy = LoudestStrategy(min_audio_level=0.8)
- candidates = [
- ArbitrationCandidate(satellite_id="s1", audio_level=0.5),
- ArbitrationCandidate(satellite_id="s2", audio_level=0.6),
- ]
- winner = strategy.select(candidates)
- assert winner is None
- def test_rank_candidates(self):
- """Test Kandidaten-Ranking."""
- strategy = LoudestStrategy()
- candidates = [
- ArbitrationCandidate(satellite_id="c", audio_level=0.3),
- ArbitrationCandidate(satellite_id="a", audio_level=0.9),
- ArbitrationCandidate(satellite_id="b", audio_level=0.6),
- ]
- ranked = strategy.rank(candidates)
- assert ranked[0].satellite_id == "a"
- assert ranked[1].satellite_id == "b"
- assert ranked[2].satellite_id == "c"
- class TestConfidenceStrategy:
- """Tests für ConfidenceStrategy."""
- def test_select_highest_confidence(self):
- """Test dass höchste Confidence gewählt wird."""
- strategy = ConfidenceStrategy()
- candidates = [
- ArbitrationCandidate(satellite_id="low", confidence=0.6),
- ArbitrationCandidate(satellite_id="high", confidence=0.95),
- ArbitrationCandidate(satellite_id="mid", confidence=0.8),
- ]
- winner = strategy.select(candidates)
- assert winner is not None
- assert winner.satellite_id == "high"
- def test_min_confidence(self):
- """Test Minimum Confidence Filter."""
- strategy = ConfidenceStrategy(min_confidence=0.7)
- candidates = [
- ArbitrationCandidate(satellite_id="too_low", confidence=0.5),
- ArbitrationCandidate(satellite_id="ok", confidence=0.8),
- ]
- winner = strategy.select(candidates)
- assert winner is not None
- assert winner.satellite_id == "ok"
- class TestCombinedStrategy:
- """Tests für CombinedStrategy."""
- def test_combined_score(self):
- """Test kombinierte Bewertung."""
- weights = CombinedWeights(
- audio_level=0.5,
- confidence=0.5,
- recency=0.0,
- )
- strategy = CombinedStrategy(weights=weights)
- candidates = [
- ArbitrationCandidate(
- satellite_id="balanced",
- audio_level=0.8,
- confidence=0.8,
- ),
- ArbitrationCandidate(
- satellite_id="loud_low_conf",
- audio_level=0.95,
- confidence=0.5,
- ),
- ]
- winner = strategy.select(candidates)
- assert winner is not None
- assert winner.satellite_id == "balanced"
- # balanced: 0.8*0.5 + 0.8*0.5 = 0.8
- # loud: 0.95*0.5 + 0.5*0.5 = 0.725
- def test_weights_normalization(self):
- """Test dass Gewichte normalisiert werden."""
- weights = CombinedWeights(
- audio_level=2.0,
- confidence=2.0,
- recency=1.0,
- )
- # Sollte auf 0.4, 0.4, 0.2 normalisiert werden
- assert abs(weights.audio_level - 0.4) < 0.01
- assert abs(weights.confidence - 0.4) < 0.01
- assert abs(weights.recency - 0.2) < 0.01
- def test_score_details(self):
- """Test dass Score-Details gespeichert werden."""
- strategy = CombinedStrategy()
- candidate = ArbitrationCandidate(
- satellite_id="test",
- audio_level=0.8,
- confidence=0.9,
- )
- score = strategy.calculate_score(candidate)
- assert "audio_level" in candidate.score_details
- assert "confidence" in candidate.score_details
- assert "audio_weighted" in candidate.score_details
- class TestProximityStrategy:
- """Tests für ProximityStrategy."""
- def test_last_active_bonus(self):
- """Test Bonus für letzten aktiven Raum."""
- room_relevance = RoomRelevance(last_active_bonus=0.3)
- strategy = ProximityStrategy(room_relevance=room_relevance)
- candidates = [
- ArbitrationCandidate(
- satellite_id="kitchen",
- room_id="kitchen",
- audio_level=0.8,
- confidence=0.8,
- ),
- ArbitrationCandidate(
- satellite_id="living",
- room_id="living_room",
- audio_level=0.75,
- confidence=0.8,
- ),
- ]
- context = {"last_active_room": "living_room"}
- winner = strategy.select(candidates, context)
- # Living room sollte wegen Bonus gewinnen trotz niedrigerem audio_level
- assert winner is not None
- # Der Bonus könnte ausreichen um das niedrigere audio_level auszugleichen
- assert "history_bonus" in winner.score_details
- def test_adjacent_rooms_bonus(self):
- """Test Bonus für benachbarte Räume."""
- room_relevance = RoomRelevance(
- adjacent_rooms={"living_room": ["hallway", "kitchen"]},
- adjacent_bonus=0.2,
- )
- strategy = ProximityStrategy(room_relevance=room_relevance)
- candidates = [
- ArbitrationCandidate(
- satellite_id="hallway",
- room_id="hallway",
- audio_level=0.7,
- confidence=0.8,
- ),
- ]
- context = {"last_active_room": "living_room"}
- strategy.calculate_score(candidates[0], context)
- # Hallway sollte room_relevance Bonus bekommen
- assert candidates[0].score_details["room_relevance"] > 0.5
- # =============================================================================
- # WakewordArbitrator Tests
- # =============================================================================
- class TestWakewordArbitrator:
- """Tests für WakewordArbitrator Service."""
- def test_create_arbitrator(self):
- """Test Arbitrator-Erstellung."""
- config = ArbitrationConfig(
- strategy="loudest",
- window_duration_ms=500,
- )
- arbitrator = WakewordArbitrator(config)
- assert arbitrator.config.strategy == "loudest"
- assert arbitrator.config.window_duration_ms == 500
- assert arbitrator.strategy.name == "loudest"
- def test_create_with_combined_strategy(self):
- """Test Arbitrator mit Combined-Strategie."""
- config = ArbitrationConfig(
- strategy="combined",
- audio_weight=0.7,
- confidence_weight=0.2,
- recency_weight=0.1,
- )
- arbitrator = WakewordArbitrator(config)
- assert arbitrator.strategy.name == "combined"
- def test_is_arbitrating_initial(self):
- """Test dass initial nicht arbitriert wird."""
- arbitrator = WakewordArbitrator()
- assert not arbitrator.is_arbitrating
- @pytest.mark.asyncio
- async def test_add_candidate_opens_window(self):
- """Test dass add_candidate Fenster öffnet."""
- # Verwende kurzes Fenster und warte nicht auf cancel
- config = ArbitrationConfig(
- window_duration_ms=50,
- min_wait_ms=10,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- candidate = ArbitrationCandidate(
- satellite_id="test",
- audio_level=0.8,
- )
- await arbitrator.add_candidate(candidate)
- # Prüfe sofort (bevor Timer abläuft)
- assert arbitrator.is_arbitrating
- assert len(arbitrator.current_candidates) == 1
- # Warte auf natürliches Ende statt cancel
- await asyncio.sleep(0.1)
- @pytest.mark.asyncio
- async def test_multiple_candidates(self):
- """Test mehrere Kandidaten hinzufügen."""
- config = ArbitrationConfig(
- window_duration_ms=100,
- min_wait_ms=20,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="s1", audio_level=0.7)
- )
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="s2", audio_level=0.9)
- )
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="s3", audio_level=0.6)
- )
- assert len(arbitrator.current_candidates) == 3
- # Warte auf natürliches Ende
- await asyncio.sleep(0.15)
- @pytest.mark.asyncio
- async def test_decision_callback(self):
- """Test Decision-Callback."""
- results = []
- async def on_decision(result: ArbitrationResult):
- results.append(result)
- config = ArbitrationConfig(
- window_duration_ms=100,
- min_wait_ms=50,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- arbitrator.on_decision(on_decision)
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="s1", audio_level=0.7, confidence=0.8)
- )
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="s2", audio_level=0.9, confidence=0.85)
- )
- # Warten auf Entscheidung
- await asyncio.sleep(0.2)
- assert len(results) == 1
- assert results[0].success
- assert results[0].winner.satellite_id == "s2"
- assert len(results[0].rejected) == 1
- @pytest.mark.asyncio
- async def test_cancel_current(self):
- """Test aktuelle Arbitration abbrechen."""
- config = ArbitrationConfig(
- window_duration_ms=100,
- min_wait_ms=20,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="test", audio_level=0.8)
- )
- assert arbitrator.is_arbitrating
- # Cancel sollte sofort funktionieren
- cancelled = await arbitrator.cancel_current()
- assert cancelled
- # Nach cancel sollte nicht mehr arbitrierend sein
- # (oder warten auf natürliches Ende)
- await asyncio.sleep(0.05)
- # is_arbitrating prüft ob window noch offen ist
- # Nach cancel ist es geschlossen
- def test_stats_initial(self):
- """Test initiale Statistiken."""
- arbitrator = WakewordArbitrator()
- stats = arbitrator.stats
- assert stats["total_arbitrations"] == 0
- assert stats["successful"] == 0
- def test_get_window_status_closed(self):
- """Test Fenster-Status wenn geschlossen."""
- arbitrator = WakewordArbitrator()
- status = arbitrator.get_window_status()
- assert status["state"] == "closed"
- assert status["candidates"] == 0
- @pytest.mark.asyncio
- async def test_get_window_status_open(self):
- """Test Fenster-Status wenn offen."""
- config = ArbitrationConfig(
- window_duration_ms=100,
- min_wait_ms=20,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- await arbitrator.add_candidate(
- ArbitrationCandidate(satellite_id="test", audio_level=0.8)
- )
- status = arbitrator.get_window_status()
- assert status["state"] == "open"
- assert status["candidates"] == 1
- assert "duration_ms" in status
- assert "remaining_ms" in status
- assert "arbitration_id" in status
- # Warte auf natürliches Ende
- await asyncio.sleep(0.15)
- def test_change_strategy(self):
- """Test Strategie wechseln."""
- arbitrator = WakewordArbitrator()
- assert arbitrator.strategy.name == "combined" # Default
- arbitrator.strategy = LoudestStrategy()
- assert arbitrator.strategy.name == "loudest"
- class TestArbitrationResult:
- """Tests für ArbitrationResult."""
- def test_create_result(self):
- """Test Result-Erstellung."""
- winner = ArbitrationCandidate(satellite_id="winner", audio_level=0.9)
- rejected = [
- ArbitrationCandidate(satellite_id="loser", audio_level=0.5)
- ]
- result = ArbitrationResult(
- success=True,
- winner=winner,
- rejected=rejected,
- conversation_id="conv_123",
- arbitration_id="arb_456",
- candidate_count=2,
- strategy_used="loudest",
- )
- assert result.success
- assert result.winner.satellite_id == "winner"
- assert len(result.rejected) == 1
- assert result.conversation_id == "conv_123"
- def test_result_to_dict(self):
- """Test Dictionary-Konvertierung."""
- winner = ArbitrationCandidate(satellite_id="winner")
- result = ArbitrationResult(
- success=True,
- winner=winner,
- candidate_count=1,
- strategy_used="loudest",
- )
- data = result.to_dict()
- assert data["success"] is True
- assert data["winner"]["satellite_id"] == "winner"
- assert data["strategy_used"] == "loudest"
- # =============================================================================
- # Integration Tests
- # =============================================================================
- class TestArbitrationIntegration:
- """Integration-Tests für das Arbitration-System."""
- @pytest.mark.asyncio
- async def test_full_arbitration_flow(self):
- """Test kompletter Arbitration-Ablauf."""
- results = []
- async def capture_result(result):
- results.append(result)
- config = ArbitrationConfig(
- strategy="combined",
- window_duration_ms=100,
- min_wait_ms=20,
- audio_weight=0.6,
- confidence_weight=0.3,
- recency_weight=0.1,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- arbitrator.on_decision(capture_result)
- # Simuliere Wakeword-Erkennungen von verschiedenen Satellites
- await arbitrator.add_candidate(ArbitrationCandidate(
- satellite_id="kitchen",
- room_id="kitchen",
- audio_level=0.7,
- confidence=0.85,
- ))
- await arbitrator.add_candidate(ArbitrationCandidate(
- satellite_id="living_room",
- room_id="living_room",
- audio_level=0.85,
- confidence=0.9,
- ))
- await arbitrator.add_candidate(ArbitrationCandidate(
- satellite_id="hallway",
- room_id="hallway",
- audio_level=0.6,
- confidence=0.8,
- ))
- # Warten auf Entscheidung
- await asyncio.sleep(0.2)
- assert len(results) == 1
- result = results[0]
- assert result.success
- assert result.winner is not None
- assert result.candidate_count == 3
- assert len(result.rejected) == 2
- # Living room sollte gewinnen (höchstes audio_level und confidence)
- assert result.winner.satellite_id == "living_room"
- @pytest.mark.asyncio
- async def test_single_candidate_wins(self):
- """Test dass einzelner Kandidat gewinnt."""
- results = []
- async def capture_result(result):
- results.append(result)
- config = ArbitrationConfig(
- window_duration_ms=80,
- min_wait_ms=20,
- auto_start_conversation=False,
- notify_rejected=False,
- )
- arbitrator = WakewordArbitrator(config)
- arbitrator.on_decision(capture_result)
- await arbitrator.add_candidate(ArbitrationCandidate(
- satellite_id="only_one",
- audio_level=0.8,
- confidence=0.9,
- ))
- await asyncio.sleep(0.15)
- assert len(results) == 1
- assert results[0].success
- assert results[0].winner.satellite_id == "only_one"
- assert len(results[0].rejected) == 0
- @pytest.mark.asyncio
- async def test_strategy_comparison(self):
- """Test verschiedene Strategien mit gleichen Kandidaten."""
- candidates = [
- ArbitrationCandidate(
- satellite_id="loud_low_conf",
- audio_level=0.95,
- confidence=0.6,
- ),
- ArbitrationCandidate(
- satellite_id="quiet_high_conf",
- audio_level=0.6,
- confidence=0.95,
- ),
- ]
- # Loudest sollte loud_low_conf wählen
- loudest = LoudestStrategy()
- winner1 = loudest.select(candidates.copy())
- assert winner1.satellite_id == "loud_low_conf"
- # Confidence sollte quiet_high_conf wählen
- confidence = ConfidenceStrategy()
- winner2 = confidence.select(candidates.copy())
- assert winner2.satellite_id == "quiet_high_conf"
|