Skip to content

Commit 0204fc5

Browse files
JarbasAlclaude
andcommitted
test: verify skill install from ggwave audio end-to-end
Wire the real ggwave audio transformer plugin and SkillsStore onto a single bus and feed a genuine ggwave waveform carrying a GHS: payload; assert the decoded payload drives ovos.skills.install through to a pip install request (pip and GitHub validation mocked). Also assert the allow_pip=false path refuses the install. Adds ovos-dinkum-listener and the ggwave plugin to the test extra and bumps the ovoscope floor to >=0.20.0 for feed_audio_stream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ad67abd commit 0204fc5

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ test = [
4646
"ovos-skill-hello-world>=0.2.5a2",
4747
"ovos-skill-parrot>=0.1.27a6",
4848
"ovos-skill-fallback-unknown>=0.1.10a2",
49+
# data-over-sound install path: ggwave audio -> skill installer
50+
"ovos-dinkum-listener>=0.8.2a1,<1.0.0",
51+
"ovos-audio-transformer-plugin-ggwave>=1.0.0a1,<2.0.0",
4952
]
5053
mycroft = [
5154
"ovos_PHAL[extras]>=0.2.16a1,<1.0.0",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2024 OpenVoiceOS
2+
# Licensed under the Apache License, Version 2.0
3+
"""End-to-end test: install a skill from *ggwave audio* through ovos-core.
4+
5+
Wires the real ggwave audio transformer plugin and ovos-core's ``SkillsStore``
6+
onto a single bus, then feeds a genuine ggwave waveform carrying a ``GHS:``
7+
(GitHub-skill) payload. The decoded payload must drive ``SkillsStore`` all the
8+
way to a pip install request — proving the data-over-sound skill-install path
9+
works end-to-end. ``pip_install`` and the GitHub validation call are mocked, so
10+
nothing is fetched or installed.
11+
12+
Run:
13+
uv run pytest test/end2end/test_ggwave_skill_install.py -v
14+
"""
15+
import unittest
16+
from unittest.mock import MagicMock
17+
18+
import ggwave
19+
import numpy as np
20+
from ovoscope.listener import get_mini_listener
21+
22+
from ovos_core.skill_installer import SkillsStore
23+
24+
PLUGIN_NAME = "ovos-audio-transformer-plugin-ggwave"
25+
GGWAVE_RATE = 48000
26+
27+
28+
def _ggwave_audio(payload: str, sample_rate: int = 16000) -> bytes:
29+
"""Return *payload* as real ggwave audio in int16 PCM at *sample_rate*."""
30+
waveform = ggwave.encode(payload, protocolId=1, volume=20)
31+
f32_48k = np.frombuffer(waveform, dtype=np.float32)
32+
n_out = int(round(len(f32_48k) * sample_rate / GGWAVE_RATE))
33+
f32 = np.interp(np.linspace(0, len(f32_48k) - 1, n_out),
34+
np.arange(len(f32_48k)), f32_48k).astype(np.float32)
35+
return (np.clip(f32, -1.0, 1.0) * 32767).astype("<i2").tobytes()
36+
37+
38+
class TestGGWaveSkillInstall(unittest.TestCase):
39+
"""A GHS: ggwave payload installs a skill via ovos-core's SkillsStore."""
40+
41+
def setUp(self):
42+
from ovos_audio_transformer_plugin_ggwave import GGWavePlugin
43+
44+
plugin = GGWavePlugin(
45+
config={"start_enabled": True, "sample_rate": 16000}
46+
)
47+
self.listener = get_mini_listener(
48+
plugin_instances={PLUGIN_NAME: plugin}
49+
)
50+
# SkillsStore listens on the SAME bus the plugin emits on
51+
self.store = SkillsStore(self.listener.bus, config={"allow_pip": True})
52+
# never touch the network or pip
53+
self.store.pip_install = MagicMock(return_value=True)
54+
self.store.validate_skill = MagicMock(return_value=True)
55+
56+
def tearDown(self):
57+
self.store.shutdown()
58+
self.listener.shutdown()
59+
60+
def test_ghs_audio_triggers_skill_install(self):
61+
audio = _ggwave_audio("GHS:OpenVoiceOS/skill-hello-world")
62+
msgs = self.listener.feed_audio_stream(audio, chunk_size=2048)
63+
types = [m.msg_type for m in msgs]
64+
65+
# the plugin decoded the payload and asked the installer to install it
66+
installs = [m.data for m in msgs if m.msg_type == "ovos.skills.install"]
67+
self.assertTrue(installs, f"no install request emitted; got {types}")
68+
self.assertEqual(
69+
installs[0]["url"],
70+
"https://github.com/OpenVoiceOS/skill-hello-world",
71+
)
72+
73+
# the installer ran pip exactly once for the git URL...
74+
self.store.pip_install.assert_called_once_with(
75+
["git+https://github.com/OpenVoiceOS/skill-hello-world"]
76+
)
77+
# ...and reported success back on the bus
78+
self.assertIn("ovos.skills.install.complete", types)
79+
80+
def test_install_blocked_when_pip_disabled(self):
81+
"""With allow_pip off, the same audio is refused and pip never runs."""
82+
self.store.config = {"allow_pip": False}
83+
audio = _ggwave_audio("GHS:OpenVoiceOS/skill-hello-world")
84+
msgs = self.listener.feed_audio_stream(audio, chunk_size=2048)
85+
types = [m.msg_type for m in msgs]
86+
87+
self.store.pip_install.assert_not_called()
88+
self.assertIn("ovos.skills.install.failed", types)
89+
90+
91+
if __name__ == "__main__":
92+
unittest.main()

0 commit comments

Comments
 (0)