diff --git a/ovos_utils/fakebus.py b/ovos_utils/fakebus.py index cc80c98f..44899a03 100644 --- a/ovos_utils/fakebus.py +++ b/ovos_utils/fakebus.py @@ -4,7 +4,6 @@ from threading import Event from ovos_utils.log import LOG, log_deprecation -from ovos_spec_tools import NamespaceTranslator from pyee import EventEmitter @@ -17,16 +16,11 @@ def dig_for_message(): return None -# sentinel: lets us tell "kwarg not passed" apart from "kwarg passed True/False" -_UNSET = object() - - -def _bus_flag(env_var, config_key, default=True): +def _bus_flag(env_var, config_key, default=False): """Resolve a boolean bus flag the way ``MessageBusClient._bus_flag`` does. Precedence: env var (when set) > ``websocket.`` in ovos_config - > ``default``. The env var wins when set to a truthy/falsy string; ovos_config - is optional, so any failure to read it falls back to ``default``. + > ``default``. Kept layering-clean: mirrors ``ovos_bus_client.client.client._bus_flag`` without importing from bus-client (bus-client depends on utils, not vice-versa). @@ -41,20 +35,53 @@ def _bus_flag(env_var, config_key, default=True): return default -def _resolve_bus_flags(kwargs): - """Build the namespace ``NamespaceTranslator`` for a fake bus instance. - - An explicitly-passed ``modernize``/``emit_legacy`` kwarg wins (back-compat for - callers passing ``emit_legacy=True/False``); otherwise the flag is resolved via - env var -> ``websocket.*`` config -> default ``True``, matching the real client. +# --- the legacy wire bridge is GONE ----------------------------------------- +# +# The fake bus used to mirror ``MessageBusClient``'s two migration bridges: the +# OVOS-MSG-1 namespace bridge (a spec topic also reached listeners on the +# legacy topic it replaced, and the reverse) and the OVOS-INTENT-4 intent-topic +# bridge (a canonical ``:`` dispatch also reached the old +# ``:.intent`` spelling). Both were removed from the real +# client, so both are removed here — a test double that kept them would let a +# harness pass against behaviour the fleet no longer has. + +#: Bus flags that used to steer the bridge. +_REMOVED_BRIDGE_FLAGS = ( + ("OVOS_BUS_EMIT_LEGACY", "emit_legacy"), + ("OVOS_BUS_MODERNIZE", "modernize"), + ("OVOS_BUS_INTENT_REEMIT_BLANKET", "intent_reemit_blanket"), +) + + +def _reject_removed_bridge_flags(kwargs): + """Handle a caller that still asks for the removed bridge. + + The two spellings get different treatment on purpose. + + An **env var or config entry** is an operator asking a live deployment to + keep the legacy topics on the wire. That belief is now wrong, and silence + would hand them a fleet that drops messages, so it raises — the same error + the real ``MessageBusClient`` raises. + + A **constructor kwarg** is a test harness, not a deployment. Harnesses + across the ecosystem pass ``emit_legacy=True`` unconditionally (ovoscope's + ``MiniCroft`` is one), and raising there would break every one of them at + construction without telling anybody anything useful. The kwarg is + accepted, ignored, and warned about instead. """ - modernize = kwargs.get("modernize", _UNSET) - if modernize is _UNSET: - modernize = _bus_flag("OVOS_BUS_MODERNIZE", "modernize", default=True) - emit_legacy = kwargs.get("emit_legacy", _UNSET) - if emit_legacy is _UNSET: - emit_legacy = _bus_flag("OVOS_BUS_EMIT_LEGACY", "emit_legacy", default=True) - return NamespaceTranslator(modernize=modernize, emit_legacy=emit_legacy) + for env_var, config_key in _REMOVED_BRIDGE_FLAGS: + if _bus_flag(env_var, config_key, default=False): + raise RuntimeError( + f"'{config_key}' (env {env_var}) is enabled, but the legacy " + "wire bridge was removed from the fake bus, matching " + "ovos-bus-client. Legacy bus topics are no longer emitted or " + "mirrored. Migrate the producers and consumers to the " + f"OVOS-MSG-1 spec topics, then unset '{config_key}'.") + if kwargs.get(config_key): + log_deprecation( + f"the '{config_key}' kwarg no longer does anything: the " + "legacy wire bridge was removed from the fake bus. Drop it " + "from the call.", "1.0.0") class FakeBus: @@ -63,13 +90,9 @@ def __init__(self, *args, **kwargs): self.session_id = "default" self.ee = kwargs.get("emitter") or EventEmitter() self.ee.on("error", self.on_error) - # mirror MessageBusClient's namespace migration so the test/satellite - # double bridges legacy<->ovos.* topics identically. Flags resolve the - # same way the real client does: explicit modernize=/emit_legacy= kwarg - # wins, else env var -> websocket.* config -> default on. - self._translator = _resolve_bus_flags(kwargs) - self._handler_guards = {} # handler -> shared mirror-guard - self._dedup_registrations = {} # handler -> [(msg_type, wrapped), ...] + # the migration window is over: no namespace bridge, no intent-topic + # twin, no mirror-guard — matching MessageBusClient. + _reject_removed_bridge_flags(kwargs) self.on_open() try: self.session_id = kwargs["session"].session_id @@ -80,22 +103,6 @@ def __init__(self, *args, **kwargs): self.on_default_session_update) def on(self, msg_type, handler): - # wrap handlers on migrated topics so a handler subscribed to both the - # legacy and ovos.* topic fires once (the mirror is dropped) - if self._translator.is_migrated(msg_type): - guard = self._handler_guards.get(handler) - if guard is None: - guard = self._translator.new_mirror_guard() - self._handler_guards[handler] = guard - - def wrapped(message=None): - if guard(message): - return - return handler(message) - - self.ee.on(msg_type, wrapped) - self._dedup_registrations.setdefault(handler, []).append((msg_type, wrapped)) - return self.ee.on(msg_type, handler) def once(self, msg_type, handler): @@ -126,20 +133,6 @@ def emit(self, message): self.ee.emit(message.msg_type, message) except Exception as e: LOG.exception(f"Error in event handler for '{message.msg_type}': {e}") - # namespace migration: also dispatch the counterpart topic(s) so a - # listener on either namespace receives the event (consumers dedupe). - # the mirrored payload is reshaped into the counterpart topic's shape - # (identity for payload-compatible renames, a per-topic transform for - # shape-changing ones) so a listener on it receives the payload in *its* - # shape -- matching MessageBusClient's bridge. - for topic in self._translator.counterpart_topics(message.msg_type): - try: - translated = self._translator.translate_payload( - from_topic=message.msg_type, to_topic=topic, - data=message.data) - self.ee.emit(topic, message.forward(topic, translated)) - except Exception as e: - LOG.exception(f"Error in counterpart dispatch for '{topic}': {e}") def on_message(self, *args): """ @@ -228,18 +221,6 @@ def rcv(m): return msg def remove(self, msg_type, handler): - regs = self._dedup_registrations.get(handler) - if regs: - for ev, wrapped in [r for r in regs if r[0] == msg_type]: - try: - self.ee.remove_listener(ev, wrapped) - except Exception: - pass - regs.remove((ev, wrapped)) - if not regs: - self._dedup_registrations.pop(handler, None) - self._handler_guards.pop(handler, None) - return try: self.ee.remove_listener(msg_type, handler) except Exception: @@ -357,7 +338,7 @@ def __new__(cls, *args, **kwargs): return FakeMessage(*args, **kwargs) -class AsyncFakeBus: +class AsyncFakeBus(): """In-process stand-in for ``AsyncMessageBusClient``. Mirrors the same surface as the real async bus client: ``connect`` / @@ -381,10 +362,8 @@ def __init__(self, *args, **kwargs): self.session_id = "default" self.ee = kwargs.get("emitter") or EventEmitter() self.ee.on("error", self.on_error) - # mirror MessageBusClient's namespace migration (see FakeBus.__init__). - self._translator = _resolve_bus_flags(kwargs) - self._handler_guards = {} # handler -> shared mirror-guard - self._dedup_registrations = {} # handler -> [(msg_type, wrapped), ...] + # no legacy wire bridge (see FakeBus.__init__). + _reject_removed_bridge_flags(kwargs) self.connected_event = asyncio.Event() self.connected_event.set() self.on_open() @@ -401,41 +380,12 @@ def __init__(self, *args, **kwargs): # ------------------------------------------------------------------ def on(self, msg_type, handler): - # wrap handlers on migrated topics so a handler subscribed to both the - # legacy and ovos.* topic fires once (the mirror is dropped) -- same as - # FakeBus.on / MessageBusClient.on. - if self._translator.is_migrated(msg_type): - guard = self._handler_guards.get(handler) - if guard is None: - guard = self._translator.new_mirror_guard() - self._handler_guards[handler] = guard - - def wrapped(message=None): - if guard(message): - return - return handler(message) - - self.ee.on(msg_type, wrapped) - self._dedup_registrations.setdefault(handler, []).append((msg_type, wrapped)) - return self.ee.on(msg_type, handler) def once(self, msg_type, handler): self.ee.once(msg_type, handler) def remove(self, msg_type, handler): - regs = self._dedup_registrations.get(handler) - if regs: - for ev, wrapped in [r for r in regs if r[0] == msg_type]: - try: - self.ee.remove_listener(ev, wrapped) - except Exception: - pass - regs.remove((ev, wrapped)) - if not regs: - self._dedup_registrations.pop(handler, None) - self._handler_guards.pop(handler, None) - return try: self.ee.remove_listener(msg_type, handler) except Exception: @@ -483,16 +433,6 @@ async def emit(self, message): self.ee.emit(message.msg_type, message) except Exception as e: LOG.exception(f"Error in event handler for '{message.msg_type}': {e}") - # namespace migration: also dispatch the counterpart topic(s) with the - # payload reshaped into each counterpart's shape -- same as FakeBus.emit. - for topic in self._translator.counterpart_topics(message.msg_type): - try: - translated = self._translator.translate_payload( - from_topic=message.msg_type, to_topic=topic, - data=message.data) - self.ee.emit(topic, message.forward(topic, translated)) - except Exception as e: - LOG.exception(f"Error in counterpart dispatch for '{topic}': {e}") # ------------------------------------------------------------------ # Sync helpers used internally — same as FakeBus diff --git a/pyproject.toml b/pyproject.toml index 597e101a..1071640b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "rich-click~=1.7", "rich~=13.7", "python-dateutil", - "ovos-spec-tools>=0.16.1a2", + "ovos-spec-tools>=1.6.0a2", # intent_topics helpers for the FakeBus legacy re-emit ] [project.urls] diff --git a/test/unittests/log_test/configured.log b/test/unittests/log_test/configured.log index c889d3fa..66ca1d13 100644 --- a/test/unittests/log_test/configured.log +++ b/test/unittests/log_test/configured.log @@ -1,126 +1,59 @@ -2026-03-11 04:12:11.787 - configured - ovos_utils.network_utils:get_external_ip:78 - ERROR - Got resp=503: -2026-03-11 04:12:11.789 - configured - ovos_utils.network_utils:get_external_ip:80 - ERROR - Unable to get external IP Address: network error -2026-03-11 04:12:11.796 - configured - ovos_utils.network_utils:check_captive_portal:162 - ERROR - Error checking for captive portal +2026-08-01 14:14:22.438 - configured - ovos_utils.dialog:load_dialogs - WARNING - Deprecation version=1.0.0. Caller=test_dialog:191. use 'ovos_spec_tools.LocaleResources' to load .dialog resources +2026-08-01 14:14:22.443 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:191. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.455 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:202. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.460 - configured - ovos_utils.dialog:load_dialogs - WARNING - Deprecation version=1.0.0. Caller=test_dialog:203. use 'ovos_spec_tools.LocaleResources' to load .dialog resources +2026-08-01 14:14:22.468 - configured - ovos_utils.dialog:load_dialogs - WARNING - Deprecation version=1.0.0. Caller=test_dialog:184. use 'ovos_spec_tools.LocaleResources' to load .dialog resources +2026-08-01 14:14:22.472 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:184. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.481 - configured - ovos_utils.dialog:load_dialogs - WARNING - Deprecation version=1.0.0. Caller=test_dialog:213. use 'ovos_spec_tools.LocaleResources' to load .dialog resources +2026-08-01 14:14:22.486 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:213. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.493 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:70. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.501 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:41. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.507 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:151. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.515 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:135. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.523 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:100. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.530 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:29. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.538 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:56. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.546 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:85. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.561 - configured - ovos_utils.dialog:get_dialog - WARNING - Deprecation version=1.0.0. Caller=test_dialog:248. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.574 - configured - ovos_utils.dialog:get_dialog - WARNING - Deprecation version=1.0.0. Caller=test_dialog:224. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.586 - configured - ovos_utils.dialog:get_dialog - WARNING - Deprecation version=1.0.0. Caller=test_dialog:263. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.599 - configured - ovos_utils.dialog:get_dialog - WARNING - Deprecation version=1.0.0. Caller=test_dialog:237. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.604 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_dialog:237. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:22.629 - configured - ovos_utils.messagebus::5 - WARNING - Deprecation version=1.0.0. Caller=test_messagebus:37. ovos_utils.messagebus has been deprecated since version 0.1.0!! please import from ovos_utils.fakebus or ovos_bus_client directly +2026-08-01 14:14:22.654 - configured - ovos_utils.messagebus::5 - WARNING - Deprecation version=1.0.0. Caller=test_messagebus:30. ovos_utils.messagebus has been deprecated since version 0.1.0!! please import from ovos_utils.fakebus or ovos_bus_client directly +2026-08-01 14:14:22.783 - configured - ovos_utils.ocp:available_extractors - WARNING - Deprecation version=0.1.0. Caller=test_ocp:402. import ovos_utils.available_extractors from ovos_plugin_manager.ocp instead +2026-08-01 14:14:23.002 - configured - ovos_utils.events:EventSchedulerInterface.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_events:530. EventSchedulerInterface moved to ovos_bus_client. 'from ovos_bus_client.apis.events import EventSchedulerInterface' +2026-08-01 14:14:26.083 - configured - ovos_utils.events:EventSchedulerInterface.__init__ - WARNING - Deprecation version=1.0.0. Caller=test_events:200. EventSchedulerInterface moved to ovos_bus_client. 'from ovos_bus_client.apis.events import EventSchedulerInterface' +2026-08-01 14:14:26.138 - configured - ovos_utils.lang:get_language_dir - WARNING - Deprecation version=1.0.0. Caller=test_lang:89. use 'closest_lang' from 'ovos_spec_tools' (or 'ovos_spec_tools.LocaleResources') +2026-08-01 14:14:26.144 - configured - ovos_utils.lang:get_language_dir - WARNING - Deprecation version=1.0.0. Caller=test_lang:71. use 'closest_lang' from 'ovos_spec_tools' (or 'ovos_spec_tools.LocaleResources') +2026-08-01 14:14:26.160 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:59. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.164 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:61. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.171 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:34. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.175 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:35. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.181 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:49. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.186 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:50. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.192 - configured - ovos_utils.lang:standardize_lang_tag - WARNING - Deprecation version=1.0.0. Caller=test_lang:42. use 'standardize_lang' from 'ovos_spec_tools' instead +2026-08-01 14:14:26.241 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=dialog.test_dialog:27. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:26.260 - configured - ovos_utils.dialog:load_dialogs - WARNING - Deprecation version=1.0.0. Caller=dialog.test_dialog:100. use 'ovos_spec_tools.LocaleResources' to load .dialog resources +2026-08-01 14:14:26.264 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=dialog.test_dialog:100. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:26.293 - configured - ovos_utils.dialog:load_dialogs - WARNING - Deprecation version=1.0.0. Caller=dialog.test_dialog:94. use 'ovos_spec_tools.LocaleResources' to load .dialog resources +2026-08-01 14:14:26.298 - configured - ovos_utils.dialog:MustacheDialogRenderer.__init__ - WARNING - Deprecation version=1.0.0. Caller=dialog.test_dialog:94. use the OVOS-INTENT-2 §4.2 dialog renderer in 'ovos_spec_tools' ('render' / 'DialogRenderer') +2026-08-01 14:14:26.560 - configured - ovos_utils.sound:_find_player:66 - ERROR - Can't find player for: test.xyz +2026-08-01 14:14:26.575 - configured - ovos_utils.sound:play_audio:107 - ERROR - Failed to play: No playback functionality available +2026-08-01 14:14:26.583 - configured - ovos_utils.sound:play_audio:119 - ERROR - Failed to play: ['broken', 'test.mp3'] +2026-08-01 14:14:26.587 - configured - ovos_utils.sound:play_audio:120 - ERROR - no such file Traceback (most recent call last): - File "/home/miro/PycharmProjects/OpenVoiceOS Workspace/ovos-utils/ovos_utils/network_utils.py", line 156, in check_captive_portal - html_doc = requests.get(host).text - ~~~~~~~~~~~~^^^^^^ - File "/home/miro/.local/share/uv/python/cpython-3.13.12-linux-x86_64-gnu/lib/python3.13/unittest/mock.py", line 1169, in __call__ + File "/home/miro/tmp/ovos-utils-compat-drop/ovos_utils/sound.py", line 117, in play_audio + return subprocess.Popen(play_cmd, env=environment) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/miro/.local/share/uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/unittest/mock.py", line 1139, in __call__ return self._mock_call(*args, **kwargs) - ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ - File "/home/miro/.local/share/uv/python/cpython-3.13.12-linux-x86_64-gnu/lib/python3.13/unittest/mock.py", line 1173, in _mock_call + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/miro/.local/share/uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/unittest/mock.py", line 1143, in _mock_call return self._execute_mock_call(*args, **kwargs) - ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ - File "/home/miro/.local/share/uv/python/cpython-3.13.12-linux-x86_64-gnu/lib/python3.13/unittest/mock.py", line 1228, in _execute_mock_call + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/miro/.local/share/uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/unittest/mock.py", line 1198, in _execute_mock_call raise effect -Exception: timeout -2026-03-11 04:12:11.823 - configured - ovos_utils.ocp:from_dict:254 - ERROR - track dictionary does not contain 'uri', it is not a valid MediaEntry -2026-03-11 04:12:11.824 - configured - ovos_utils.ocp:from_dict:256 - WARNING - DEPRECATED: use dict2entry() for Playlists and PluginStreams, MediaEntry.from_dict is only for regular media, will start throwing ValueError in 0.1.0 -2026-03-11 04:12:11.837 - configured - ovos_utils.ocp:available_extractors:165 - ERROR - please install/update ovos_plugin_manager -2026-03-11 04:12:11.841 - configured - ovos_utils.ocp:goto_track:586 - DEBUG - New playlist position: 1 -2026-03-11 04:12:11.842 - configured - ovos_utils.ocp:goto_track:586 - DEBUG - New playlist position: 0 -2026-03-11 04:12:11.842 - configured - ovos_utils.ocp:goto_track:586 - DEBUG - New playlist position: 0 -2026-03-11 04:12:11.843 - configured - ovos_utils.ocp:goto_track:588 - ERROR - requested track not in the playlist: MediaEntry(uri='http://missing.com/x.mp3', title='', artist='', match_confidence=0, skill_id='ovos.common_play', playback=, status=, media_type=, length=0, image='', skill_icon='', javascript='') -2026-03-11 04:12:11.852 - configured - ovos_utils.ocp:goto_track:586 - DEBUG - New playlist position: 2 -2026-03-11 04:12:11.853 - configured - ovos_utils.ocp:goto_track:586 - DEBUG - New playlist position: 1 -2026-03-11 04:12:11.854 - configured - ovos_utils.ocp:goto_track:588 - ERROR - requested track not in the playlist: MediaEntry(uri='nonexistent', title='nonexistent', artist='', match_confidence=0, skill_id='ovos.common_play', playback=, status=, media_type=, length=0, image='', skill_icon='', javascript='') -2026-03-11 04:12:11.857 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (3! Going to start of playlist -2026-03-11 04:12:11.858 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (-1! Going to start of playlist -2026-03-11 04:12:11.859 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (-1! Going to start of playlist -2026-03-11 04:12:11.860 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (100! Going to start of playlist -2026-03-11 04:12:11.877 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (0! Going to start of playlist -2026-03-11 04:12:11.878 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (-1! Going to start of playlist -2026-03-11 04:12:11.878 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (1! Going to start of playlist -2026-03-11 04:12:11.879 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (1! Going to start of playlist -2026-03-11 04:12:11.880 - configured - ovos_utils.ocp:_validate_position:607 - ERROR - Playlist pointer is in an invalid position (10! Going to start of playlist -2026-03-11 04:12:11.882 - configured - ovos_utils.parse:_validate_matching_strategy:27 - ERROR - rapidfuzz is not installed, falling back to SequenceMatcher for all match strategies -2026-03-11 04:12:11.882 - configured - ovos_utils.parse:_validate_matching_strategy:29 - WARNING - pip install rapidfuzz -2026-03-11 04:12:11.904 - configured - ovos_utils.security:decrypt:93 - ERROR - run pip install pycryptodomex -2026-03-11 04:12:11.906 - configured - ovos_utils.security:decrypt:103 - ERROR - decryption failed, invalid key? -2026-03-11 04:12:11.910 - configured - ovos_utils.security:encrypt:80 - ERROR - run pip install pycryptodomex -2026-03-11 04:12:11.912 - configured - ovos_utils.security:create_self_signed_cert:34 - ERROR - run pip install pyopenssl -2026-03-11 04:12:11.917 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.918 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'my_service' -2026-03-11 04:12:11.918 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.919 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.920 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.921 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.923 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.924 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.924 - configured - ovos_utils.skill_installer:handle_install_python:391 - ERROR - pip disabled in mycroft.conf -2026-03-11 04:12:11.925 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.926 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.927 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.928 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.929 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.931 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.932 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.932 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.933 - configured - ovos_utils.skill_installer:handle_uninstall_python:427 - ERROR - pip disabled in mycroft.conf -2026-03-11 04:12:11.934 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.934 - configured - ovos_utils.skill_installer:pip_install:230 - ERROR - no package list provided to install -2026-03-11 04:12:11.935 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.937 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.938 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [ovos_test] (pip) Installing my-pkg -2026-03-11 04:12:11.938 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /usr/bin/uv pip install -c http://example.com/c.txt my-pkg -2026-03-11 04:12:11.939 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.940 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [ovos_test] (pip) Installing my-pkg -2026-03-11 04:12:11.940 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/PycharmProjects/OpenVoiceOS Workspace/ovos-utils/.venv/bin/python3 -m pip install -c http://example.com/c.txt my-pkg -2026-03-11 04:12:11.941 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.942 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [ovos_test] (pip) Installing bad-pkg -2026-03-11 04:12:11.943 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c https://raw.githubusercontent.com/OpenVoiceOS/ovos-releases/refs/heads/main/constraints-stable.txt bad-pkg -2026-03-11 04:12:11.944 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.945 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [ovos_test] (pip) Installing pkg -2026-03-11 04:12:11.945 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c http://x.com/c.txt pkg -2026-03-11 04:12:11.947 - configured - ovos_utils.skill_installer:validate_constraints:205 - ERROR - Couldn't find the constraints file -2026-03-11 04:12:11.948 - configured - ovos_utils.skill_installer:validate_constraints:191 - DEBUG - Constraints url: http://example.com/c.txt -2026-03-11 04:12:11.949 - configured - ovos_utils.skill_installer:validate_constraints:191 - DEBUG - Constraints url: http://example.com/missing.txt -2026-03-11 04:12:11.949 - configured - ovos_utils.skill_installer:validate_constraints:195 - ERROR - Remote constraints file not accessible: 404 -2026-03-11 04:12:11.950 - configured - ovos_utils.skill_installer:validate_constraints:191 - DEBUG - Constraints url: http://example.com/c.txt -2026-03-11 04:12:11.951 - configured - ovos_utils.skill_installer:validate_constraints:201 - ERROR - Error accessing remote constraints: timeout -2026-03-11 04:12:11.952 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.953 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.954 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.954 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [svc] (pip) Installing pkg -2026-03-11 04:12:11.955 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c http://x.com/c.txt pkg -2026-03-11 04:12:11.956 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.956 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [svc] (pip) Installing pkg -2026-03-11 04:12:11.957 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c http://x.com/c.txt --break-system-packages pkg -2026-03-11 04:12:11.958 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.959 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [svc] (pip) Installing pkg -2026-03-11 04:12:11.959 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c http://x.com/c.txt --pre pkg -2026-03-11 04:12:11.960 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.961 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [svc] (pip) Installing pkg -2026-03-11 04:12:11.961 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c http://x.com/c.txt pkg -2026-03-11 04:12:11.962 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:11.963 - configured - ovos_utils.skill_installer:pip_install:256 - INFO - [svc] (pip) Installing pkg -2026-03-11 04:12:11.963 - configured - ovos_utils.skill_installer:pip_install:258 - DEBUG - /home/miro/.local/bin/uv pip install -c http://x.com/c.txt pkg -2026-03-11 04:12:11.964 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.965 - configured - ovos_utils.skill_installer:pip_uninstall:295 - ERROR - no package list provided to uninstall -2026-03-11 04:12:11.966 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:11.967 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'ovos_test' -2026-03-11 04:12:12.298 - configured - ovos_utils.skill_installer:pip_uninstall:330 - ERROR - tried to uninstall a protected package: ['ovos-core'] -2026-03-11 04:12:12.299 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:12.461 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:12.462 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /usr/bin/uv pip uninstall -y custom-pkg -2026-03-11 04:12:12.464 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:12.925 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:12.926 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /home/miro/PycharmProjects/OpenVoiceOS Workspace/ovos-utils/.venv/bin/python3 -m pip uninstall -y custom-pkg -2026-03-11 04:12:12.929 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:13.262 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:13.263 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /home/miro/.local/bin/uv pip uninstall -y custom-pkg -2026-03-11 04:12:13.267 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:13.269 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:13.294 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /home/miro/.local/bin/uv pip uninstall -y custom-pkg -2026-03-11 04:12:13.296 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:13.296 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:13.297 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /home/miro/.local/bin/uv pip uninstall -y custom-pkg -2026-03-11 04:12:13.298 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:13.569 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:13.570 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /home/miro/.local/bin/uv pip uninstall -y --break-system-packages custom-pkg -2026-03-11 04:12:13.571 - configured - ovos_utils.skill_installer:__init__:146 - INFO - ServiceInstaller registered for service 'svc' -2026-03-11 04:12:13.859 - configured - ovos_utils.skill_installer:pip_uninstall:344 - INFO - [svc] (pip) Uninstalling custom-pkg -2026-03-11 04:12:13.859 - configured - ovos_utils.skill_installer:pip_uninstall:348 - DEBUG - /home/miro/.local/bin/uv pip uninstall -y custom-pkg -2026-03-11 04:12:13.913 - configured - ovos_utils.system:system_reboot:83 - DEBUG - sudo systemctl reboot -i -2026-03-11 04:12:13.915 - configured - ovos_utils.system:system_shutdown:66 - DEBUG - sudo systemctl poweroff -i -2026-03-11 04:12:13.916 - configured - ovos_utils.system:system_shutdown:66 - DEBUG - systemctl poweroff -i +OSError: no such file +2026-08-01 14:14:26.598 - configured - ovos_utils.security:create_self_signed_cert - WARNING - Deprecation version=1.0.0. Caller=test_security_extra:68. create_self_signed_cert is unmaintained and generates a 1024-bit RSA / SHA-1 certificate that modern OpenSSL (SECLEVEL=2, the Debian/Ubuntu/Fedora default) refuses to load ('EE_KEY_TOO_SMALL'). Callers should bundle their own self-signed cert generation (RSA >= 2048, SHA-256, with a Subject Alternative Name) instead of relying on this helper. +2026-08-01 14:14:26.604 - configured - ovos_utils.security:create_self_signed_cert - WARNING - Deprecation version=1.0.0. Caller=test_security_extra:49. create_self_signed_cert is unmaintained and generates a 1024-bit RSA / SHA-1 certificate that modern OpenSSL (SECLEVEL=2, the Debian/Ubuntu/Fedora default) refuses to load ('EE_KEY_TOO_SMALL'). Callers should bundle their own self-signed cert generation (RSA >= 2048, SHA-256, with a Subject Alternative Name) instead of relying on this helper. diff --git a/test/unittests/log_test/rotate.log b/test/unittests/log_test/rotate.log deleted file mode 100644 index dd7d75aa..00000000 --- a/test/unittests/log_test/rotate.log +++ /dev/null @@ -1 +0,0 @@ -2026-03-11 04:12:13.924 - rotate - ovos_utils.system:ssh_enable - WARNING - Deprecation version=0.2.0. Caller=test_system:165. DEPRECATED: use ovos-PHAL-plugin-system diff --git a/test/unittests/log_test/rotate.log.1 b/test/unittests/log_test/rotate.log.1 deleted file mode 100644 index 8f4286ae..00000000 --- a/test/unittests/log_test/rotate.log.1 +++ /dev/null @@ -1 +0,0 @@ -2026-03-11 04:12:13.923 - rotate - ovos_utils.system:ssh_disable - WARNING - Deprecation version=0.2.0. Caller=test_system:175. DEPRECATED: use ovos-PHAL-plugin-system diff --git a/test/unittests/test_async_fakebus.py b/test/unittests/test_async_fakebus.py index a3a1f79d..a4babe51 100644 --- a/test/unittests/test_async_fakebus.py +++ b/test/unittests/test_async_fakebus.py @@ -198,56 +198,58 @@ def test_run_in_thread_alias(self): self.assertTrue(bus.started_running) -class TestAsyncFakeBusNamespaceMigration(unittest.TestCase): - """AsyncFakeBus mirrors FakeBus / MessageBusClient namespace migration.""" +class TestAsyncFakeBusNoLegacyBridge(unittest.TestCase): + """AsyncFakeBus mirrors MessageBusClient: the legacy bridge is gone.""" - def test_legacy_emit_reaches_spec_listener(self): - bus = AsyncFakeBus() # both flags default on + def test_legacy_emit_does_not_reach_spec_listener(self): + bus = AsyncFakeBus() got = [] bus.on("ovos.utterance.speak", lambda m: got.append(m.msg_type)) _run(bus.emit(FakeMessage("speak", {"utterance": "hi"}))) - self.assertEqual(got, ["ovos.utterance.speak"]) # modernize bridged it + self.assertEqual(got, []) - def test_spec_emit_reaches_legacy_listener(self): + def test_spec_emit_does_not_reach_legacy_listener(self): bus = AsyncFakeBus() got = [] bus.on("speak", lambda m: got.append(m.msg_type)) _run(bus.emit(FakeMessage("ovos.utterance.speak", {"utterance": "hi"}))) - self.assertEqual(got, ["speak"]) # emit_legacy bridged it + self.assertEqual(got, []) - def test_counterpart_payload_is_translated(self): - # a spec listener on the counterpart of a SHAPE-CHANGING legacy topic - # receives the payload reshaped into ITS shape. detach_intent -> - # ovos.intent.deregister splits "skill:intent" into skill_id/intent_name. + def test_no_payload_reshaping_for_shape_changing_renames(self): + # detach_intent -> ovos.intent.deregister used to be reshaped for a + # listener on the counterpart. There is no counterpart delivery now. bus = AsyncFakeBus() got = [] bus.on("ovos.intent.deregister", lambda m: got.append(dict(m.data))) _run(bus.emit(FakeMessage("detach_intent", {"intent_name": "skill.foo:HelloIntent"}))) - self.assertEqual(got, [{"skill_id": "skill.foo", "intent_name": "HelloIntent"}]) + self.assertEqual(got, []) - def test_dual_listener_fires_once(self): + def test_dual_listener_fires_per_real_emit(self): + # no mirror means no mirror-guard: two subscriptions to two topics are + # two ordinary subscriptions. bus = AsyncFakeBus() calls = [] handler = lambda m: calls.append(m.msg_type) bus.on("speak", handler) bus.on("ovos.utterance.speak", handler) _run(bus.emit(FakeMessage("speak", {"utterance": "hi"}))) - self.assertEqual(len(calls), 1) # mirror deduped + self.assertEqual(calls, ["speak"]) + _run(bus.emit(FakeMessage("ovos.utterance.speak", {"utterance": "hi"}))) + self.assertEqual(calls, ["speak", "ovos.utterance.speak"]) - def test_distinct_listeners_each_fire_once(self): + def test_spec_listener_receives_the_spec_topic(self): bus = AsyncFakeBus() - legacy, spec = [], [] - bus.on("speak", lambda m: legacy.append(1)) + spec = [] bus.on("ovos.utterance.speak", lambda m: spec.append(1)) - _run(bus.emit(FakeMessage("speak", {"utterance": "hi"}))) - self.assertEqual((len(legacy), len(spec)), (1, 1)) + _run(bus.emit(FakeMessage("ovos.utterance.speak", {"utterance": "hi"}))) + self.assertEqual(len(spec), 1) - def test_flags_off_no_bridging(self): - bus = AsyncFakeBus(modernize=False, emit_legacy=False) + def test_a_removed_flag_kwarg_is_ignored_not_fatal(self): + bus = AsyncFakeBus(emit_legacy=True) got = [] - bus.on("ovos.utterance.speak", lambda m: got.append(m.msg_type)) - _run(bus.emit(FakeMessage("speak", {"utterance": "hi"}))) + bus.on("speak", lambda m: got.append(m.msg_type)) + _run(bus.emit(FakeMessage("ovos.utterance.speak", {"utterance": "hi"}))) self.assertEqual(got, []) def test_remove_cleans_up(self): @@ -255,10 +257,7 @@ def test_remove_cleans_up(self): calls = [] handler = lambda m: calls.append(1) bus.on("speak", handler) - bus.on("ovos.utterance.speak", handler) bus.remove("speak", handler) - bus.remove("ovos.utterance.speak", handler) - self.assertNotIn(handler, bus._handler_guards) _run(bus.emit(FakeMessage("speak", {"utterance": "hi"}))) self.assertEqual(calls, []) diff --git a/test/unittests/test_fakebus_namespace_migration.py b/test/unittests/test_fakebus_namespace_migration.py deleted file mode 100644 index 3eb4b79b..00000000 --- a/test/unittests/test_fakebus_namespace_migration.py +++ /dev/null @@ -1,150 +0,0 @@ -"""FakeBus mirrors MessageBusClient's legacy<->ovos.* namespace migration, so -e2e/satellite tests exercise the real cross-namespace behaviour.""" -import asyncio -import unittest -from unittest.mock import patch - -from ovos_utils.fakebus import AsyncFakeBus, FakeBus, Message - - -def _run(coro): - return asyncio.run(coro) - - -class TestFakeBusNamespaceMigration(unittest.TestCase): - def test_legacy_emit_reaches_spec_listener(self): - bus = FakeBus() # both flags default on - got = [] - bus.on("ovos.utterance.speak", lambda m: got.append(m.msg_type)) - bus.emit(Message("speak", {"utterance": "hi"})) - self.assertEqual(got, ["ovos.utterance.speak"]) # modernize bridged it - - def test_spec_emit_reaches_legacy_listener(self): - bus = FakeBus() - got = [] - bus.on("speak", lambda m: got.append(m.msg_type)) - bus.emit(Message("ovos.utterance.speak", {"utterance": "hi"})) - self.assertEqual(got, ["speak"]) # emit_legacy bridged it - - def test_dual_listener_fires_once(self): - bus = FakeBus() - calls = [] - handler = lambda m: calls.append(m.msg_type) - bus.on("speak", handler) - bus.on("ovos.utterance.speak", handler) - bus.emit(Message("speak", {"utterance": "hi"})) - self.assertEqual(len(calls), 1) # mirror deduped - - def test_distinct_listeners_each_fire_once(self): - bus = FakeBus() - legacy, spec = [], [] - bus.on("speak", lambda m: legacy.append(1)) - bus.on("ovos.utterance.speak", lambda m: spec.append(1)) - bus.emit(Message("speak", {"utterance": "hi"})) - self.assertEqual((len(legacy), len(spec)), (1, 1)) - - def test_flags_off_no_bridging(self): - bus = FakeBus(modernize=False, emit_legacy=False) - got = [] - bus.on("ovos.utterance.speak", lambda m: got.append(m.msg_type)) - bus.emit(Message("speak", {"utterance": "hi"})) - self.assertEqual(got, []) # no translation -> spec listener not reached - - def test_unmapped_topic_untouched(self): - bus = FakeBus() - got = [] - bus.on("my.custom.topic", lambda m: got.append(m.msg_type)) - bus.emit(Message("my.custom.topic", {"x": 1})) - self.assertEqual(got, ["my.custom.topic"]) - - def test_shape_changing_payload_reshaped_for_spec_listener(self): - # a spec listener on the counterpart of a SHAPE-CHANGING legacy topic - # receives the payload in ITS shape, not a verbatim legacy copy. - # detach_intent -> ovos.intent.deregister splits the compound - # "skill:intent" name into skill_id + intent_name. - bus = FakeBus() - got = [] - bus.on("ovos.intent.deregister", lambda m: got.append(dict(m.data))) - bus.emit(Message("detach_intent", {"intent_name": "skill.foo:HelloIntent"})) - self.assertEqual(len(got), 1) - self.assertEqual(got[0], {"skill_id": "skill.foo", "intent_name": "HelloIntent"}) - - def test_shape_changing_payload_reshaped_for_legacy_listener(self): - bus = FakeBus() - got = [] - bus.on("detach_intent", lambda m: got.append(dict(m.data))) - bus.emit(Message("ovos.intent.deregister", - {"skill_id": "skill.foo", "intent_name": "HelloIntent"})) - self.assertEqual(len(got), 1) - # rejoined to the legacy compound shape - self.assertEqual(got[0].get("intent_name"), "skill.foo:HelloIntent") - - def test_payload_compatible_rename_delivered_equivalent(self): - bus = FakeBus() - got = [] - bus.on("ovos.utterance.speak", lambda m: got.append(dict(m.data))) - bus.emit(Message("speak", {"utterance": "hi", "lang": "en-us"})) - self.assertEqual(got, [{"utterance": "hi", "lang": "en-us"}]) # identity - - def test_remove_cleans_up(self): - bus = FakeBus() - calls = [] - handler = lambda m: calls.append(1) - bus.on("speak", handler) - bus.on("ovos.utterance.speak", handler) - bus.remove("speak", handler) - bus.remove("ovos.utterance.speak", handler) - self.assertNotIn(handler, bus._handler_guards) - bus.emit(Message("speak", {"utterance": "hi"})) - self.assertEqual(calls, []) - - -class TestFakeBusFlagResolution(unittest.TestCase): - """When the kwarg is omitted, flags resolve via env -> websocket.* config -> - default True, matching MessageBusClient._bus_flag. An explicit kwarg wins.""" - - def _legacy_mirrored(self, bus): - # emit a legacy topic; if emit_legacy bridging is on a spec listener fires - got = [] - bus.on("ovos.utterance.speak", lambda m: got.append(m.msg_type)) - if isinstance(bus, AsyncFakeBus): - _run(bus.emit(Message("speak", {"utterance": "hi"}))) - else: - bus.emit(Message("speak", {"utterance": "hi"})) - return got == ["ovos.utterance.speak"] - - def test_default_true_no_env_mirrors(self): - with patch.dict("os.environ", {}, clear=False): - import os - os.environ.pop("OVOS_BUS_MODERNIZE", None) - os.environ.pop("OVOS_BUS_EMIT_LEGACY", None) - self.assertTrue(self._legacy_mirrored(FakeBus())) - self.assertTrue(self._legacy_mirrored(AsyncFakeBus())) - - def test_env_false_disables_mirror(self): - with patch.dict("os.environ", - {"OVOS_BUS_MODERNIZE": "false", - "OVOS_BUS_EMIT_LEGACY": "false"}): - self.assertFalse(self._legacy_mirrored(FakeBus())) - self.assertFalse(self._legacy_mirrored(AsyncFakeBus())) - - def test_explicit_kwarg_beats_env(self): - # env says off, but an explicit modernize=True kwarg still mirrors - with patch.dict("os.environ", - {"OVOS_BUS_MODERNIZE": "false", - "OVOS_BUS_EMIT_LEGACY": "false"}): - self.assertTrue(self._legacy_mirrored(FakeBus(modernize=True))) - self.assertTrue(self._legacy_mirrored(AsyncFakeBus(modernize=True))) - - def test_explicit_false_kwarg_beats_unset_env(self): - import os - with patch.dict("os.environ", {}, clear=False): - os.environ.pop("OVOS_BUS_MODERNIZE", None) - os.environ.pop("OVOS_BUS_EMIT_LEGACY", None) - # default would mirror; explicit modernize=False suppresses it - self.assertFalse(self._legacy_mirrored(FakeBus(modernize=False))) - self.assertFalse(self._legacy_mirrored(AsyncFakeBus(modernize=False))) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/unittests/test_fakebus_no_legacy_compat.py b/test/unittests/test_fakebus_no_legacy_compat.py new file mode 100644 index 00000000..09ffed36 --- /dev/null +++ b/test/unittests/test_fakebus_no_legacy_compat.py @@ -0,0 +1,143 @@ +"""Guards for the post-compat world: FakeBus carries no legacy wire bridge. + +These are the inverted twins of the old +``test_fakebus_namespace_migration.py`` and +``test_fakebus_intent_legacy_reemit.py``. Those files proved the bridge +worked; this one proves it is absent. + +A test double that kept the bridge would be worse than useless — every +harness built on it would pass against behaviour the real +``MessageBusClient`` no longer has. +""" +import unittest +from unittest.mock import patch + +from ovos_utils import fakebus as fakebus_mod +from ovos_utils.fakebus import FakeBus, FakeMessage +from ovos_spec_tools import MIGRATION_MAP + +LEGACY_SPEAK = "speak" +SPEC_SPEAK = MIGRATION_MAP[LEGACY_SPEAK].value +SKILL_ID = "ovos-skill-fake.openvoiceos" +CANONICAL_INTENT = f"{SKILL_ID}:food.order" +LEGACY_INTENT = f"{CANONICAL_INTENT}.intent" + + +class TestNoNamespaceBridge(unittest.TestCase): + def test_spec_emit_does_not_reach_legacy_listeners(self): + bus = FakeBus() + got = [] + bus.on(LEGACY_SPEAK, lambda m: got.append(m.msg_type)) + bus.emit(FakeMessage(SPEC_SPEAK, {"utterance": "hi"})) + self.assertEqual(got, []) + + def test_legacy_emit_does_not_reach_spec_listeners(self): + bus = FakeBus() + got = [] + bus.on(SPEC_SPEAK, lambda m: got.append(m.msg_type)) + bus.emit(FakeMessage(LEGACY_SPEAK, {"utterance": "hi"})) + self.assertEqual(got, []) + + def test_no_migrated_topic_is_bridged_in_either_direction(self): + """Sweep the whole map rather than trusting one sample pair.""" + for legacy, spec in MIGRATION_MAP.items(): + with self.subTest(topic=legacy): + bus = FakeBus() + seen = [] + bus.on(legacy, lambda m: seen.append("legacy")) + bus.on(spec.value, lambda m: seen.append("spec")) + bus.emit(FakeMessage(spec.value)) + self.assertEqual(seen, ["spec"]) + seen.clear() + bus.emit(FakeMessage(legacy)) + self.assertEqual(seen, ["legacy"]) + + def test_handler_on_both_namespaces_is_no_longer_deduped(self): + bus = FakeBus() + calls = [] + + def handler(message): + calls.append(message.msg_type) + + bus.on(LEGACY_SPEAK, handler) + bus.on(SPEC_SPEAK, handler) + bus.emit(FakeMessage(SPEC_SPEAK)) + bus.emit(FakeMessage(LEGACY_SPEAK)) + self.assertEqual(calls, [SPEC_SPEAK, LEGACY_SPEAK]) + + def test_on_registers_the_handler_itself_not_a_wrapper(self): + bus = FakeBus() + + def handler(message): + pass + + bus.on(SPEC_SPEAK, handler) + self.assertEqual(bus.ee.listeners(SPEC_SPEAK), [handler]) + bus.remove(SPEC_SPEAK, handler) + self.assertEqual(bus.ee.listeners(SPEC_SPEAK), []) + + +class TestNoIntentTopicTwin(unittest.TestCase): + def test_canonical_dispatch_does_not_reach_the_suffixed_twin(self): + bus = FakeBus() + got = [] + bus.on(LEGACY_INTENT, lambda m: got.append(m.msg_type)) + bus.emit(FakeMessage(CANONICAL_INTENT, {"utterance": "order food"})) + self.assertEqual(got, []) + + def test_canonical_listener_still_receives_the_canonical_dispatch(self): + bus = FakeBus() + got = [] + bus.on(CANONICAL_INTENT, lambda m: got.append(m.msg_type)) + bus.emit(FakeMessage(CANONICAL_INTENT, {"utterance": "order food"})) + self.assertEqual(got, [CANONICAL_INTENT]) + + def test_no_reemit_marker_is_stamped_on_the_dispatch(self): + bus = FakeBus() + got = [] + bus.on(CANONICAL_INTENT, lambda m: got.append(m)) + bus.emit(FakeMessage(CANONICAL_INTENT)) + self.assertNotIn("__legacy_intent_reemit__", got[0].context) + + +class TestBridgeSurfaceIsGone(unittest.TestCase): + def test_module_exports_no_bridge_symbols(self): + for name in ("INTENT_REEMIT_CONTEXT_KEY", "IntentAliasRegistry", + "legacy_reemit_targets", "NamespaceTranslator", + "_LegacyIntentBridge", "_resolve_bus_flags"): + self.assertFalse(hasattr(fakebus_mod, name), name) + + def test_instances_carry_no_bridge_state(self): + bus = FakeBus() + for name in ("_translator", "_handler_guards", "_dedup_registrations", + "_intent_aliases", "_intent_reemit_blanket"): + self.assertFalse(hasattr(bus, name), name) + + +class TestRemovedFlagsAreLoud(unittest.TestCase): + def test_kwarg_is_accepted_and_ignored(self): + """A harness kwarg is not an operator decision — it warns, not raises, + so every ecosystem harness that still passes it keeps booting.""" + for key in ("emit_legacy", "modernize", "intent_reemit_blanket"): + with self.subTest(key=key): + bus = FakeBus(**{key: True}) + got = [] + bus.on(LEGACY_SPEAK, lambda m: got.append(m.msg_type)) + bus.emit(FakeMessage(SPEC_SPEAK)) + self.assertEqual(got, []) + + def test_env_flag_raises(self): + for env_var in ("OVOS_BUS_EMIT_LEGACY", "OVOS_BUS_MODERNIZE", + "OVOS_BUS_INTENT_REEMIT_BLANKET"): + with self.subTest(env_var=env_var): + with patch.dict(fakebus_mod.environ, {env_var: "true"}): + with self.assertRaises(RuntimeError): + FakeBus() + + def test_explicitly_disabled_flags_are_accepted(self): + FakeBus(emit_legacy=False, modernize=False, + intent_reemit_blanket=False) + + +if __name__ == "__main__": + unittest.main()