async def test_on_add_long_form(mock_client, event_loop, value, expected): s = AliasServer(loop=event_loop) await s.async_init() s._update_aliases = AsyncMock(event_loop) # Check defaults await s._on_add("meta/alias/add", value) s._update_aliases.assert_called_once_with({"foo/alias": expected})
async def test_on_change(mock_client, event_loop): s = AliasServer(loop=event_loop) await s.async_init() await s._on_add("meta/alias/add", ["foo/target", "foo/alias"]) assert s._aliases != {} await s._on_change("meta/alias/aliases", {}) assert s._aliases == {}
async def test_on_remove(mock_client, event_loop): s = AliasServer(loop=event_loop) await s.async_init() await s._on_add("meta/alias/add", ["foo/target", "foo/alias"]) assert set(s._aliases.keys()) == set(["foo/alias"]) await s._on_remove("meta/alias/remove", "foo/alias") assert s._aliases == {}
async def test_on_add_invalid_forms(mock_client, event_loop, arg): s = AliasServer(loop=event_loop) await s.async_init() await s._on_add("meta/alias/add", arg) assert s._aliases == {} assert mock_client.send_event.call_count == 1 assert mock_client.send_event.mock_calls[0][1][0] == "meta/alias/error"
def main(): parser = ArgumentParser( description="A service which creates aliases for Qth paths.") parser.add_argument("--cache", "-c", default="aliases.json", help="Filename of alias cache (default %(default)s).") parser.add_argument("--prefix", "-p", default="meta/alias/", help="Prefix for control events/properties " "(default %(default)s).") parser.add_argument("--host", "-H", default=None, help="Qth server hostname.") parser.add_argument("--port", "-P", default=None, type=int, help="Qth server port.") parser.add_argument("--keepalive", "-K", default=10, type=int, help="MQTT Keepalive interval (seconds).") parser.add_argument("--quiet", "-q", default=False, action="store_true", help="Only report errors.") parser.add_argument("--debug", default=False, action="store_true", help="(Development only) Run asyncio in debug mode.") parser.add_argument("--version", "-V", action="version", version="%(prog)s {}".format(__version__)) args = parser.parse_args() if not args.quiet: logging.basicConfig(level=logging.INFO) loop = asyncio.get_event_loop() if not args.debug: loop.set_debug(True) s = AliasServer(cache_file=args.cache, prefix=args.prefix, host=args.host, port=args.port, keepalive=args.keepalive, loop=loop) try: loop.run_until_complete(s.async_init()) loop.run_forever() except KeyboardInterrupt: loop.run_until_complete(s.close())
async def test_file_cache(mock_client, event_loop, tmpdir): cache_file = tmpdir.join("cache.json") cache_file.write(json.dumps({ "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test...", } })) s = AliasServer(cache_file=str(cache_file), loop=event_loop) await s.async_init() # Initial contents should be loaded and sent to Qth assert mock_client.set_property.call_count == 1 assert mock_client.set_property.mock_calls[0][1][0] == "meta/alias/aliases" await s._on_change(*mock_client.set_property.mock_calls[0][1]) assert s._aliases_json == { "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test...", } } # Upon adding a new entry this should be saved await s._on_add("meta/alias/add", ["bar/target", "bar/alias"]) assert json.loads(cache_file.read()) == { "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test...", }, "bar/alias": { "target": "bar/target", "alias": "bar/alias", "transform": None, "inverse": None, "description": "Alias of bar/target.", }, }
async def test_async_init(mock_client, event_loop): s = AliasServer(loop=event_loop) await s.async_init() # Check registrations assert {call[1][0]: call[1][1] for call in mock_client.register.mock_calls} == { "meta/alias/add": "EVENT-N:1", "meta/alias/remove": "EVENT-N:1", "meta/alias/aliases": "PROPERTY-1:N", "meta/alias/error": "EVENT-1:N", } # Check watches assert mock_client.watch_event.call_count == 2 mock_client.watch_event.assert_any_call("meta/alias/add", s._on_add) mock_client.watch_event.assert_any_call("meta/alias/remove", s._on_remove) mock_client.watch_property.assert_called_once_with( "meta/alias/aliases", s._on_change)
async def test_close(mock_client, event_loop): s = AliasServer(loop=event_loop) await s.async_init() await s._on_add("meta/alias/add", ["foo/target", "foo/alias"]) await s._aliases["foo/alias"]._on_target_registration_changed( "foo/target", [{ "behaviour": "PROPERTY-1:N", "delete_on_unregister": True, }]) await s.close() mock_client.unregister.assert_any_call("meta/alias/add") mock_client.unregister.assert_any_call("meta/alias/remove") mock_client.unregister.assert_any_call("meta/alias/aliases") mock_client.unregister.assert_any_call("meta/alias/error") mock_client.delete_property.assert_any_call("meta/alias/aliases") mock_client.delete_property.assert_any_call("foo/alias")
async def test_file_cache_dev_null(mock_client, event_loop): s = AliasServer(cache_file="/dev/null", loop=event_loop) await s.async_init() assert s._aliases == {}
async def test_update_aliases(mock_client, event_loop, monkeypatch): # Mock out the Alias objects for ease alias_objects = [] def _Alias(alias_server, **description): alias = Mock() alias.async_init = AsyncMock(event_loop) alias.delete = AsyncMock(event_loop) alias.json = description alias_objects.append(alias) return alias Alias = Mock(side_effect=_Alias) monkeypatch.setattr(qth_alias, "Alias", Alias) s = AliasServer(loop=event_loop) await s.async_init() # Staying empty should result in no change assert s._aliases == {} await s._update_aliases({}) assert s._aliases == {} assert Alias.call_count == 0 assert mock_client.set_property.call_count == 1 # Adding an alias should result in a change await s._update_aliases({ "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test alias.", }, }) assert Alias.call_count == 1 alias_foo_alias = alias_objects[0] assert s._aliases == {"foo/alias": alias_foo_alias} alias_foo_alias.async_init.assert_called_once_with() assert mock_client.set_property.call_count == 2 mock_client.set_property.assert_called_with("meta/alias/aliases", { "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test alias.", }, }) # Doing it again should do nothing... await s._update_aliases({ "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test alias.", }, }) assert Alias.call_count == 1 assert mock_client.set_property.call_count == 2 # Adding a second alias should result in a change again... await s._update_aliases({ "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test alias.", }, "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": None, "inverse": None, "description": "A test alias.", }, }) assert Alias.call_count == 2 alias_foo_alias2 = alias_objects[1] assert s._aliases == {"foo/alias": alias_foo_alias, "foo/alias2": alias_foo_alias2} alias_foo_alias2.async_init.assert_called_once_with() assert mock_client.set_property.call_count == 3 mock_client.set_property.assert_called_with("meta/alias/aliases", { "foo/alias": { "target": "foo/target", "alias": "foo/alias", "transform": None, "inverse": None, "description": "A test alias.", }, "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": None, "inverse": None, "description": "A test alias.", }, }) # Removing an alias should result in a change... await s._update_aliases({ "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": None, "inverse": None, "description": "A test alias.", }, }) assert Alias.call_count == 2 alias_foo_alias.delete.assert_called_once_with() assert s._aliases == {"foo/alias2": alias_foo_alias2} assert mock_client.set_property.call_count == 4 mock_client.set_property.assert_called_with("meta/alias/aliases", { "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": None, "inverse": None, "description": "A test alias.", }, }) # Changing an alias should result in a change... await s._update_aliases({ "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": "value / 63.0", "inverse": "int(value * 63)", "description": "A test alias.", }, }) assert Alias.call_count == 3 alias_foo_alias2.delete.assert_called_once_with() alias_foo_alias2_v2 = alias_objects[2] alias_foo_alias2_v2.async_init.assert_called_once_with() assert s._aliases == {"foo/alias2": alias_foo_alias2_v2} assert mock_client.set_property.call_count == 5 mock_client.set_property.assert_called_with("meta/alias/aliases", { "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": "value / 63.0", "inverse": "int(value * 63)", "description": "A test alias.", }, }) # Inserting a cycle should result in an error and no change... await s._update_aliases({ "foo/target": { "target": "foo/alias2", "alias": "foo/target", "transform": None, "inverse": None, "description": "A test alias.", }, "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": "value / 63.0", "inverse": "int(value * 63)", "description": "A test alias.", }, }) assert mock_client.send_event.call_count == 1 assert mock_client.send_event.mock_calls[0][1][0] == "meta/alias/error" assert Alias.call_count == 3 assert s._aliases == {"foo/alias2": alias_foo_alias2_v2} assert mock_client.set_property.call_count == 6 mock_client.set_property.assert_called_with("meta/alias/aliases", { "foo/alias2": { "target": "foo/target", "alias": "foo/alias2", "transform": "value / 63.0", "inverse": "int(value * 63)", "description": "A test alias.", }, })