Initial commit: Javis project

This commit is contained in:
Javis committed 2026-09-21 21:44:00 +02:00
commit 435c645f36
164 files changed
+35279

No files matched your search

+404
View File
@@ -0,0 +1,404 @@
"""Regression tests for the correctness fixes applied to Javis.
These target concrete, previously-broken behaviours and use only
dependency-light modules (no torch / audio / MCP) so they run fast.
"""
from __future__ import annotations
import asyncio
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from javis.core.config import Config, PathsConfig, find_project_root
from javis.llm.conversation import Conversation, count_tokens
from javis.voices.profile import VoiceProfile
class FindProjectRootTests(unittest.TestCase):
def test_finds_pyproject_marker(self) -> None:
root = find_project_root()
# The repository root holds pyproject.toml.
self.assertTrue((root / "pyproject.toml").is_file())
def test_falls_back_to_cwd_without_marker(self) -> None:
with tempfile.TemporaryDirectory() as td:
deep = Path(td) / "a" / "b"
deep.mkdir(parents=True)
# No pyproject.toml anywhere up the temp tree → cwd fallback, no raise.
result = find_project_root(deep)
self.assertIsInstance(result, Path)
class VoiceProfileCorruptionTests(unittest.TestCase):
def test_corrupt_profile_json_does_not_raise(self) -> None:
with tempfile.TemporaryDirectory() as td:
d = Path(td)
(d / "profile.json").write_text("{ this is not valid json", encoding="utf-8")
prof = VoiceProfile.from_dir(d) # must not raise
self.assertEqual(prof.name, d.name)
self.assertIsNone(prof.speed)
def test_non_numeric_speed_falls_back_to_none(self) -> None:
with tempfile.TemporaryDirectory() as td:
d = Path(td)
(d / "profile.json").write_text(
json.dumps({"name": "x", "speed": "fast"}), encoding="utf-8"
)
prof = VoiceProfile.from_dir(d)
self.assertIsNone(prof.speed)
def test_valid_speed_is_parsed(self) -> None:
with tempfile.TemporaryDirectory() as td:
d = Path(td)
(d / "profile.json").write_text(
json.dumps({"name": "x", "speed": 1.25}), encoding="utf-8"
)
prof = VoiceProfile.from_dir(d)
self.assertEqual(prof.speed, 1.25)
class ConversationTests(unittest.TestCase):
def test_history_property_is_a_readonly_snapshot(self) -> None:
conv = Conversation(system_prompt="sys", max_turns=20)
conv.add_user("hello")
snap = conv.history
snap.append({"role": "user", "content": "mutated"})
# Mutating the snapshot must not affect the real history.
self.assertEqual(len(conv.history), 1)
def test_trim_keeps_tool_pairs_and_bounds_size(self) -> None:
conv = Conversation(system_prompt="sys", max_turns=2, max_chars=200)
for i in range(10):
conv.add_user("u" * 50)
conv.add_assistant("a" * 50)
total = sum(len(m.get("content") or "") for m in conv.history)
self.assertLessEqual(total, 200)
# History must never start with an orphaned tool/assistant message.
if conv.history:
self.assertEqual(conv.history[0]["role"], "user")
def test_count_tokens_fallback_is_nonzero_for_text(self) -> None:
self.assertEqual(count_tokens(""), 0)
self.assertGreater(count_tokens("hello world this is text"), 0)
class QuickEngineTests(unittest.TestCase):
def test_load_skips_malformed_command_but_keeps_valid(self) -> None:
from javis.agent.quick_engine import QuickEngine
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "quick_commands.json"
path.write_text(json.dumps({
"version": 1,
"commands": {
"good": {
"id": "good", "trigger": "lights on", "match_mode": "exact",
"actions": [{"tool": "homey__x", "args": {}}],
},
"bad": {
"id": "bad", "trigger": "broken", "match_mode": "exact",
"actions": [{"not_a_valid_field": 1}],
},
},
}), encoding="utf-8")
eng = QuickEngine(path)
eng.load() # must not raise
ids = {c.id for c in eng.list_all()}
self.assertIn("good", ids)
self.assertNotIn("bad", ids)
class DeviceStoreDebounceTests(unittest.TestCase):
def test_touch_last_seen_debounces_disk_writes(self) -> None:
from javis.api.devices import SlaveDeviceStore
with tempfile.TemporaryDirectory() as td:
store = SlaveDeviceStore(Path(td) / "slaves.json")
store.register("dev1", "Device One")
with patch.object(store, "_save", wraps=store._save) as spy:
# Same IP, rapid calls → at most one flush within the interval.
for _ in range(5):
store.touch_last_seen("dev1", ip_address="10.0.0.1")
self.assertLessEqual(spy.call_count, 1)
def test_touch_last_seen_flushes_on_ip_change(self) -> None:
from javis.api.devices import SlaveDeviceStore
with tempfile.TemporaryDirectory() as td:
store = SlaveDeviceStore(Path(td) / "slaves.json")
store.register("dev1", "Device One")
store.touch_last_seen("dev1", ip_address="10.0.0.1")
with patch.object(store, "_save", wraps=store._save) as spy:
store.touch_last_seen("dev1", ip_address="10.0.0.2") # IP changed
self.assertEqual(spy.call_count, 1)
class TimerPersistenceTests(unittest.IsolatedAsyncioTestCase):
async def asyncNoop(self, timer_id: str, label: str) -> None:
return None
async def test_set_persists_and_cancel_updates_file(self) -> None:
from javis.core.timer import TimerService
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "timers.json"
svc = TimerService(self.asyncNoop, persist_path=path)
tid = svc.set_timer("test", 3600)
try:
self.assertTrue(path.exists())
data = json.loads(path.read_text(encoding="utf-8"))
self.assertEqual(len(data["timers"]), 1)
self.assertTrue(svc.cancel_timer(tid))
data = json.loads(path.read_text(encoding="utf-8"))
self.assertEqual(len(data["timers"]), 0)
finally:
svc.stop()
async def test_restore_reschedules_pending_and_drops_expired(self) -> None:
import time as _time
from javis.core.timer import TimerService
with tempfile.TemporaryDirectory() as td:
path = Path(td) / "timers.json"
now = _time.time()
path.write_text(json.dumps({
"version": 1,
"timers": [
{"id": "pending", "label": "future", "duration_s": 3600,
"fire_at": now + 3600},
{"id": "expired", "label": "past", "duration_s": 60,
"fire_at": now - 10},
],
}), encoding="utf-8")
svc = TimerService(self.asyncNoop, persist_path=path)
await svc.restore()
try:
active = svc.list_timers()
self.assertEqual(len(active), 1)
self.assertEqual(active[0]["id"], "pending")
finally:
svc.stop()
class FinetuneSignatureTests(unittest.IsolatedAsyncioTestCase):
async def test_run_pipeline_receives_profile_dir_not_config(self) -> None:
from javis.voices.manager import VoiceManager
import javis.voices.piper_train as pt
with tempfile.TemporaryDirectory() as td:
config = Config()
config.paths = PathsConfig(base_dir=Path(td))
config.paths.ensure()
mgr = VoiceManager(config)
prof = mgr.create("tv")
assert prof.dir is not None
(prof.dir / "training").mkdir(parents=True, exist_ok=True)
(prof.dir / "training" / "metadata.csv").write_text(
"clip_0|hallo|hallo\n", encoding="utf-8")
captured: dict = {}
def fake_run_pipeline(voice_dir, **kwargs):
captured["voice_dir"] = voice_dir
captured["kwargs"] = kwargs
return 0
from javis.voices import finetune
with patch.object(pt, "run_pipeline", fake_run_pipeline):
rc = await finetune.finetune_voice(config, "tv", language="de")
self.assertEqual(rc, 0)
self.assertEqual(captured["voice_dir"], prof.dir)
# The old bug passed the Config as voice_dir and voice_name as language.
self.assertNotIsInstance(captured["voice_dir"], Config)
self.assertEqual(captured["kwargs"].get("language"), "de")
# ── wake-word barge-in (interrupt TTS playback with the wake phrase) ──────
class BargeInConfigTests(unittest.TestCase):
"""The new WakeWordConfig.barge_in_enabled flag defaults to True."""
def test_barge_in_enabled_defaults_true(self) -> None:
from javis.core.config import WakeWordConfig
self.assertTrue(WakeWordConfig().barge_in_enabled)
class BargeInListenerTests(unittest.IsolatedAsyncioTestCase):
"""_barge_in_listener: on wake-word trigger it cancels playback, resets
the detector, sets the barge-in flag, and publishes the event."""
async def test_trigger_cancels_playback_and_sets_flag(self) -> None:
from javis.agent.orchestrator import Orchestrator
from javis.core.state import AgentState
orch = object.__new__(Orchestrator)
class FakeWake:
def __init__(self) -> None:
self.feed_calls = 0
self.reset_calls = 0
def feed(self, chunk):
self.feed_calls += 1
return 0.9 if self.feed_calls == 1 else 0.1
def triggered(self, score):
return score >= 0.5
def reset(self):
self.reset_calls += 1
class FakeMic:
def __init__(self) -> None:
self.drain_calls = 0
def drain(self):
self.drain_calls += 1
async def chunks(self, stale_timeout=None):
yield b"\x00"
class FakeBus:
def __init__(self) -> None:
self.events = []
async def publish(self, name, payload=None):
self.events.append((name, payload))
orch.wake = FakeWake()
orch.mic = FakeMic()
orch.bus = FakeBus()
orch._stop = asyncio.Event()
orch._barge_in_requested = False
orch._barge_in_task = None
interrupt_calls = []
state_calls = []
with patch.object(Orchestrator, "interrupt",
lambda self: interrupt_calls.append(1)), \
patch.object(Orchestrator, "set_state",
lambda self, s: state_calls.append(s) or asyncio.sleep(0)), \
patch.object(Orchestrator, "_play_wake_chime",
lambda self: asyncio.sleep(0)):
await orch._barge_in_listener()
# Trigger fired on the first chunk: listener must have exited.
self.assertTrue(orch._barge_in_requested)
self.assertEqual(len(interrupt_calls), 1)
self.assertEqual(state_calls, [AgentState.WAKE])
# reset() is called once at arming and once after the trigger.
self.assertEqual(orch.wake.reset_calls, 2)
self.assertEqual(orch.mic.drain_calls, 1)
self.assertIn(("barge_in", 0.9), orch.bus.events)
# Chime was scheduled via ensure_future — let it finish.
await asyncio.sleep(0)
class BargeInRunConsumptionTests(unittest.IsolatedAsyncioTestCase):
"""run(): when _barge_in_requested is set, the main loop must skip
_wait_for_wake, drain the mic tail, and start the turn with followup
grace instead of the default 1500 ms."""
async def test_run_consumes_barge_in_flag(self) -> None:
from javis.agent.orchestrator import Orchestrator
orch = object.__new__(Orchestrator)
orch.stt = object()
orch.mic = object()
orch.wake = object()
orch._stop = asyncio.Event()
orch._turn_lock = asyncio.Lock()
orch._barge_in_requested = True
orch._barge_in_task = None
orch._followup_pending = False
orch.config = Config()
orch.profiler = None
class FakeMic:
def __init__(self) -> None:
self.drain_calls = 0
def drain(self):
self.drain_calls += 1
orch.mic = FakeMic()
class FakeBus:
def __init__(self) -> None:
self.events = []
async def publish(self, name, payload=None):
self.events.append((name, payload))
orch.bus = FakeBus()
wake_calls = []
turn_calls = []
async def fake_handle_turn(self, initial_grace_ms=1500):
turn_calls.append(initial_grace_ms)
self._stop.set() # end the run() loop after the first turn
with patch.object(Orchestrator, "_wait_for_wake",
lambda self: wake_calls.append(1)), \
patch.object(Orchestrator, "_handle_turn", fake_handle_turn), \
patch.object(Orchestrator, "stop",
lambda self: asyncio.sleep(0)), \
patch.object(Orchestrator, "set_state",
lambda self, s: asyncio.sleep(0)):
await orch.run()
self.assertFalse(orch._barge_in_requested)
self.assertEqual(orch.mic.drain_calls, 1)
self.assertEqual(wake_calls, [])
self.assertEqual(turn_calls, [int(orch.config.wake_word.followup_grace_s * 1000)])
self.assertIn(("barge_in_listen", {}), orch.bus.events)
class BargeInEmptyUtteranceTests(unittest.IsolatedAsyncioTestCase):
"""_handle_turn: an empty capture must publish stt_done(empty) + stt_empty
and reset the state to IDLE (previously the GUI stayed stuck on
'listening')."""
async def test_empty_utterance_resets_to_idle(self) -> None:
from javis.agent.orchestrator import Orchestrator
from javis.core.state import AgentState
orch = object.__new__(Orchestrator)
orch.stt = object()
orch.mic = object()
orch.vad = object()
orch.config = Config()
orch._last_response_is_question = False
orch.profiler = None
class FakeBus:
def __init__(self) -> None:
self.events = []
async def publish(self, name, payload=None):
self.events.append((name, payload))
orch.bus = FakeBus()
state_calls = []
with patch("javis.agent.orchestrator.record_utterance",
new=lambda *a, **kw: asyncio.sleep(0, result=None)), \
patch.object(Orchestrator, "set_state",
lambda self, s: state_calls.append(s) or asyncio.sleep(0)):
await orch._handle_turn(initial_grace_ms=1500)
self.assertIn(("stt_done", {"text": "", "audio_s": 0.0, "latency_s": 0.0, "empty": True}),
orch.bus.events)
self.assertIn(("stt_empty", {}), orch.bus.events)
self.assertEqual(state_calls, [AgentState.LISTENING, AgentState.IDLE])
if __name__ == "__main__":
unittest.main()