mirror of
https://github.com/deunix-educ/voice-assistant.git
synced 2026-10-13 00:11:57 +02:00
d20976fea2
État du projet au premier commit : - firmware ESP32 : micro I2S, lecture anti-gigue, MQTT (état retenu, testament), mains libres, domotique de démonstration, chien de garde, présence du serveur ; - serveur Python : sessions, VAD, faster-whisper, pyannote, identification, droits par profil, domotique, mot de réveil, Piper ; - broker Mosquitto : configuration, comptes et ACL (non activés en développement) ; - étape 15b écrite, en attente d'essai : agent MQTT pour machines Linux. Étapes 1 à 15 validées. 203 tests, pyright sans erreur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.4 KiB
Python
83 lines
3.4 KiB
Python
"""Tests de l'agent Linux, étape 15b : configuration, liste blanche, exécution (sans broker)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
AGENT_DIR = Path(__file__).resolve().parents[2] / "agent"
|
|
if str(AGENT_DIR) not in sys.path:
|
|
sys.path.insert(0, str(AGENT_DIR))
|
|
|
|
from voice_agent import Action, AgentConfig, ConfigError, load_config, run_action, state_payload # noqa: E402
|
|
|
|
|
|
def config(**actions: Action) -> AgentConfig:
|
|
return AgentConfig(agent_id="pc-test", host="127.0.0.1", port=1883, username=None, password=None,
|
|
topic_prefix="agent", dry_run=False, actions=dict(actions))
|
|
|
|
|
|
def test_example_config_is_valid() -> None:
|
|
loaded = load_config(AGENT_DIR / "agent.yaml.example")
|
|
assert loaded.agent_id == "pc-bureau" and not loaded.dry_run
|
|
assert loaded.actions["shutdown"].command == ("systemctl", "poweroff")
|
|
assert loaded.topic("command") == "agent/pc-bureau/command"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("text", "message"),
|
|
[
|
|
("id: PC Bureau\nmqtt: {host: x}\n", "id « PC Bureau » invalide"),
|
|
("id: pc\nmqtt: {}\n", "mqtt.host manquant"),
|
|
# Une chaîne passerait par un shell : « poweroff; rm -rf / » serait exécutable.
|
|
("id: pc\nmqtt: {host: x}\nactions: {off: {command: 'systemctl poweroff'}}\n", "doit être une liste"),
|
|
("id: pc\nmqtt: {host: x}\nactions: {off: {command: [x], timeout_s: 0}}\n", "timeout_s"),
|
|
],
|
|
)
|
|
def test_invalid_config_is_explained(tmp_path: Path, text: str, message: str) -> None:
|
|
path = tmp_path / "agent.yaml"
|
|
path.write_text(text, encoding="utf-8")
|
|
with pytest.raises(ConfigError, match=message):
|
|
load_config(path)
|
|
|
|
|
|
def test_only_listed_actions_run() -> None:
|
|
result = run_action(config(ok=Action(("true",), 5)), "rm", "r1")
|
|
assert not result.ok and result.output == "action inconnue : rm"
|
|
|
|
|
|
def test_success_failure_and_output() -> None:
|
|
agent = config(ok=Action(("echo", "bonjour"), 5), ko=Action(("false",), 5))
|
|
ok = run_action(agent, "ok", "r1")
|
|
assert ok.ok and ok.code == 0 and ok.output == "bonjour"
|
|
ko = run_action(agent, "ko", "r2")
|
|
assert not ko.ok and ko.code == 1
|
|
|
|
|
|
def test_missing_program_and_timeout() -> None:
|
|
agent = config(absent=Action(("/nonexistent/prog",), 5), slow=Action(("sleep", "5"), 0.2))
|
|
assert run_action(agent, "absent", "r").output == "programme introuvable : /nonexistent/prog"
|
|
slow = run_action(agent, "slow", "r")
|
|
assert not slow.ok and slow.output == "arretee apres 0.2 s"
|
|
|
|
|
|
def test_dry_run_executes_nothing() -> None:
|
|
calls: list[object] = []
|
|
agent = AgentConfig(**{**config(off=Action(("systemctl", "poweroff"), 5)).__dict__, "dry_run": True})
|
|
result = run_action(agent, "off", "r", runner=lambda *args, **kwargs: calls.append(args)) # type: ignore[arg-type]
|
|
assert result.ok and result.output == "essai : systemctl poweroff" and calls == []
|
|
|
|
|
|
def test_state_lists_actions_for_the_server() -> None:
|
|
online = json.loads(state_payload(config(b=Action(("x",), 1), a=Action(("y",), 1)), True))
|
|
assert online == {"status": "online", "actions": ["a", "b"], "dry_run": False}
|
|
assert json.loads(state_payload(config(), False)) == {"status": "offline"}
|
|
|
|
|
|
def test_result_payload() -> None:
|
|
result = run_action(config(ok=Action(("true",), 5)), "ok", "w42")
|
|
assert json.loads(result.payload()) == {"action": "ok", "id": "w42", "ok": True, "code": 0, "output": ""}
|