def test_load_module_from_file_bad(): with pytest.raises(IOError) as excinfo: load_module_from_file("mqttwarn/services/unknown.py") assert ( str(excinfo.value) == "IOError: [Errno 2] No such file or directory: 'mqttwarn/services/unknown.py'" )
def test_carbon_success_value_metric_from_topic_with_leading_slash( srv, caplog): item = Item(target="test", addrs=["localhost", 2003], message="42.42", data={"topic": "/foo/bar"}) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/carbon.py") socket_mock = mock.MagicMock() module.socket.socket = socket_mock outcome = module.plugin(srv, item) assert socket_mock.mock_calls == [ call(), call().connect(("localhost", 2003)), call().sendall(mock.ANY), call().close(), ] assert outcome is True assert "Sending to carbon: foo.bar 42.42" in caplog.text
def load_services(services): for service in services: service_plugins[service] = {} service_config = cf.config('config:' + service) if service_config is None: logger.error("Service `%s' has no config section" % service) sys.exit(1) service_plugins[service]['config'] = service_config module = cf.g('config:' + service, 'module', service) if '.' in module: try: service_plugins[service]['module'] = load_module_by_name(module) logger.info('Successfully loaded service "{}" from module "{}"'.format(service, module)) except Exception as ex: logger.exception('Unable to load service "{}" from module "{}": {}'.format(service, module, ex)) else: modulefile = resource_filename('mqttwarn.services', module + '.py') try: service_plugins[service]['module'] = load_module_from_file(modulefile) logger.info('Successfully loaded service "{}"'.format(service)) except Exception as ex: logger.exception('Unable to load service "{}" from file "{}": {}'.format(service, modulefile, ex))
def test_apprise_success_no_addresses(apprise_asset, apprise_mock, srv, caplog): """ Some Apprise notifiers don't need any target address information. Proof that also works by processing an `Item` with no `target` and `addrs` attributes supplied. """ with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apprise_single.py") item = Item( config={"baseuri": "json://localhost:1234/mqtthook"}, title="⚽ Message title ⚽", message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert apprise_mock.mock_calls == [ call(asset=mock.ANY), call().add("json://localhost:1234/mqtthook"), call().notify(body="⚽ Notification message ⚽", title="⚽ Message title ⚽"), call().notify().__bool__(), ] assert outcome is True assert "Sending notification to Apprise. target=None, addresses=[]" in caplog.messages assert "Successfully sent message using Apprise" in caplog.messages
def test_apprise_success_backward_compat(apprise_asset, apprise_mock, srv, caplog): with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apprise.py") item = Item( config={"baseuri": "json://localhost:1234/mqtthook"}, title="⚽ Message title ⚽", message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert apprise_mock.mock_calls == [ call(asset=mock.ANY), call().add("json://localhost:1234/mqtthook"), call().notify(body="⚽ Notification message ⚽", title="⚽ Message title ⚽"), call().notify().__bool__(), ] assert outcome is True assert "Sending notification to Apprise. target=None, addresses=[]" in caplog.messages assert "Successfully sent message using Apprise" in caplog.messages
def test_apprise_success(apprise_asset, apprise_mock, srv, caplog): with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apprise_single.py") item = Item( config={ "baseuri": "mailtos://*****:*****@mail.example.org" }, target="test", addrs=["*****@*****.**", "*****@*****.**"], title="⚽ Message title ⚽", message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert apprise_mock.mock_calls == [ call(asset=mock.ANY), call().add( "mailtos://*****:*****@mail.example.org?to=foo%40example.org%2Cbar%40example.org" ), call().notify(body="⚽ Notification message ⚽", title="⚽ Message title ⚽"), call().notify().__bool__(), ] assert outcome is True assert ( "Sending notification to Apprise. target=test, addresses=['*****@*****.**', '*****@*****.**']" in caplog.text) assert "Successfully sent message using Apprise" in caplog.text
def test_pushover_success_with_credentials_from_environment(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={}, target="test", addrs=[None, None], message="⚽ Notification message ⚽", data={}, ) with caplog.at_level(logging.DEBUG): add_successful_mock_response() outcome = module.plugin(srv, item) assert len(responses.calls) == 1 assert ( responses.calls[0].request.url == "https://api.pushover.net/1/messages.json" ) assert ( responses.calls[0].request.body == "user=userkey2&token=appkey2&retry=60&expire=3600&message=%E2%9A%BD+Notification+message+%E2%9A%BD" ) assert responses.calls[0].request.headers["User-Agent"] == "mqttwarn" assert responses.calls[0].response.status_code == 200 assert responses.calls[0].response.text == '{"status": 1}' assert outcome is True assert "Sending pushover notification to test" in caplog.text assert "Successfully sent pushover notification" in caplog.text
def test_carbon_success_metric_value_timestamp(srv, caplog): item = Item( target="test", addrs=["localhost", 2003], message="foo 42.42 1623887596", data={}, ) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/carbon.py") socket_mock = mock.MagicMock() module.socket.socket = socket_mock outcome = module.plugin(srv, item) assert socket_mock.mock_calls == [ call(), call().connect(("localhost", 2003)), call().sendall("foo 42.42 1623887596\n"), call().close(), ] assert outcome is True assert "Sending to carbon: foo 42.42 1623887596" in caplog.text
def test_apns_success_custom_payload(mock_apns_payload, mock_apns, srv, caplog): with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apns.py") cert_file, key_file = ["cert_file", "key_file"] item = Item( target="test", addrs=[cert_file, key_file], message="⚽ Notification message ⚽", data={ "apns_token": "foobar", "payload": '{"custom": {"baz": "qux"}}', }, ) outcome = module.plugin(srv, item) assert mock_apns_payload.mock_calls == [ call( alert="⚽ Notification message ⚽", custom={"baz": "qux"}, sound="default", badge=1, ), ] assert mock_apns.mock_calls == [ mock.call(use_sandbox=False, cert_file="cert_file", key_file="key_file"), call().gateway_server.send_notification("foobar", mock.ANY), ] assert outcome is True assert "Successfully published APNS notification to foobar" in caplog.text
def test_carbon_failure_connect(srv, caplog): item = Item(target="test", addrs=["localhost", 2003], message="foo 42.42 1623887596", data={}) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/carbon.py") attrs = {"connect.side_effect": Exception("something failed")} socket_mock = mock.MagicMock() socket_mock.return_value = mock.MagicMock(**attrs) module.socket.socket = socket_mock outcome = module.plugin(srv, item) assert socket_mock.mock_calls == [ call(), call().connect(("localhost", 2003)) ] assert outcome is False assert "Sending to carbon: foo 42.42 1623887596" in caplog.text assert ( "Cannot send to carbon service localhost:2003: something failed" in caplog.text)
def test_pushover_success_with_devices(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={}, target="test", addrs=["userkey2", "appkey2", None, "cellphone1,cellphone2"], message="⚽ Notification message ⚽", data={}, ) with caplog.at_level(logging.DEBUG): add_successful_mock_response() outcome = module.plugin(srv, item) assert ( responses.calls[0].request.body == "user=userkey2&token=appkey2&retry=60&expire=3600&devices=cellphone1%2Ccellphone2&message=%E2%9A%BD+Notification+message+%E2%9A%BD" ) assert responses.calls[0].response.status_code == 200 assert responses.calls[0].response.text == '{"status": 1}' assert outcome is True assert "Sending pushover notification to test" in caplog.text assert "Successfully sent pushover notification" in caplog.text
def test_autoremote_success(srv, caplog): item = Item( target="test", addrs=["ApiKey", "Password", "Target", "Group", "TTL"], topic="autoremote/user", message="⚽ Notification message ⚽", ) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/autoremote.py") with mock.patch("requests.get") as requests_mock: outcome = module.plugin(srv, item) requests_mock.assert_called_once_with( "https://autoremotejoaomgcd.appspot.com/sendmessage", params={ "key": "ApiKey", "message": "⚽ Notification message ⚽", "target": "Target", "sender": "autoremote/user", "password": "******", "ttl": "TTL", "collapseKey": "Group", }, ) assert outcome is True assert "Sending to autoremote service" in caplog.text assert "Successfully sent to autoremote service" in caplog.text
def test_irccat_connect_fails(srv, caplog): import socket item = Item( target="test", addrs=["localhost", 12345, "#testdrive"], message="⚽ Notification message ⚽", data={}, ) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/irccat.py") # Make the call to `connect` raise an exception. mock_connection = mock.MagicMock() def error(*args, **kwargs): raise Exception("something failed") mock_connection.connect = error with mock.patch("socket.socket", side_effect=[mock_connection], create=True) as socket_mock: outcome = module.plugin(srv, item) assert socket_mock.mock_calls == [ call(socket.AF_INET, socket.SOCK_STREAM), ] assert outcome is False assert ( "Error sending IRCcat notification to test:localhost [12345]: something failed" in caplog.messages)
def test_pushover_success_with_callback_and_title_and_priority_and_alternative_message( srv, caplog ): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={"callback": "https://example.org/pushover-callback"}, target="test", addrs=["userkey2", "appkey2"], priority=555, title="⚽ Message title ⚽", message="⚽ Notification message ⚽", data={"message": "⚽ Alternative notification message ⚽"}, ) with caplog.at_level(logging.DEBUG): add_successful_mock_response() outcome = module.plugin(srv, item) assert ( responses.calls[0].request.body == "user=userkey2&token=appkey2&retry=60&expire=3600&title=%E2%9A%BD+Message+title+%E2%9A%BD&priority=555&callback=https%3A%2F%2Fexample.org%2Fpushover-callback&message=%E2%9A%BD+Alternative+notification+message+%E2%9A%BD" ) assert responses.calls[0].response.status_code == 200 assert responses.calls[0].response.text == '{"status": 1}' assert outcome is True assert "Sending pushover notification to test" in caplog.text assert "Successfully sent pushover notification" in caplog.text
def test_pushover_failure_response_error(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={}, target="test", addrs=["userkey2", "appkey2"], message="⚽ Notification message ⚽", data={}, ) with caplog.at_level(logging.DEBUG): add_failed_mock_response() outcome = module.plugin(srv, item) assert len(responses.calls) == 1 assert responses.calls[ 0].request.url == "https://api.pushover.net/1/messages.json" assert ( responses.calls[0].request.body == "user=userkey2&token=appkey2&retry=60&expire=3600&message=%E2%9A%BD+Notification+message+%E2%9A%BD" ) assert responses.calls[0].request.headers["User-Agent"] == "mqttwarn" assert responses.calls[0].response.status_code == 400 assert responses.calls[0].response.text == '{"status": 999}' assert outcome is False assert "Sending pushover notification to test" in caplog.text assert "Error sending pushover notification: b'{\"status\": 999}'" in caplog.text
def test_asterisk_failure_login_invalid(asterisk_mock, srv, caplog): with caplog.at_level(logging.DEBUG): attrs = {"login.side_effect": ManagerAuthException("something failed")} asterisk_mock.return_value = mock.MagicMock(**attrs) module = load_module_from_file("mqttwarn/services/asterisk.py") item = Item( config={ "host": "asterisk.example.org", "port": 5038, "username": "******", "password": "******", "extension": 2222, "context": "default", }, target="test", addrs=["SIP/avaya/", "0123456789"], message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert asterisk_mock.mock_calls == [ call(), call().connect("asterisk.example.org", 5038), call().login("foobar", "bazqux"), call().close(), ] assert outcome is False assert "Error logging in to the manager: something failed" in caplog.text
def test_apprise_success_with_sender(apprise_asset, apprise_mock, srv, caplog): with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apprise.py") item = Item( config={ "baseuri": "mailtos://*****:*****@mail.example.org", "sender": "*****@*****.**", "sender_name": "Max Mustermann", }, target="test", addrs=["*****@*****.**", "*****@*****.**"], title="⚽ Message title ⚽", message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert apprise_mock.mock_calls == [ call(asset=mock.ANY), call().add( "mailtos://*****:*****@[email protected]&[email protected],[email protected]&name=Max Mustermann" ), call().notify(body="⚽ Notification message ⚽", title="⚽ Message title ⚽"), call().notify().__bool__(), ] assert outcome is True assert "Successfully sent message using Apprise" in caplog.text
def test_apprise_failure_no_addresses(apprise_asset, apprise_mock, srv, caplog): with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apprise.py") item = Item( config={ "baseuri": "mailtos://*****:*****@mail.example.org" }, target="test", addrs=[], title="⚽ Message title ⚽", message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert apprise_mock.mock_calls == [] assert outcome is False assert ( "Skipped sending notification to Apprise test, no addresses configured" in caplog.text)
def test_irccat_red_success(srv, caplog): import socket item = Item( target="test", addrs=["localhost", 12345, "#testdrive"], message="⚽ Notification message ⚽", data={}, priority=2, ) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/irccat.py") socket_mock = mock.MagicMock() module.socket.socket = socket_mock outcome = module.plugin(srv, item) assert socket_mock.mock_calls == [ call(socket.AF_INET, socket.SOCK_STREAM), call().connect(("localhost", 12345)), call().send( b"%RED\xe2\x9a\xbd Notification message \xe2\x9a\xbd\n"), call().close(), ] assert outcome is True assert "Sending to IRCcat: %RED⚽ Notification message ⚽" in caplog.messages
def test_pushover_success_with_imagebase64(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") image = open("./assets/pushover.png", "rb").read() item = Item( config={}, target="test", addrs=["userkey2", "appkey2"], message="⚽ Notification message ⚽", data={"imagebase64": base64.encodebytes(image)}, ) with caplog.at_level(logging.DEBUG): add_successful_mock_response() outcome = module.plugin(srv, item) # Check response status. assert responses.calls[0].response.status_code == 200 assert responses.calls[0].response.text == '{"status": 1}' # Decode multipart request. request = responses.calls[0].request decoder = MultipartDecoder(request.body, request.headers["Content-Type"]) content_disposition_headers = [] contents = {} for part in decoder.parts: content_disposition_headers.append(part.headers[b"Content-Disposition"]) key = part.headers[b"Content-Disposition"] contents[key] = part.content # Proof request has all body parts. assert content_disposition_headers == [ b'form-data; name="user"', b'form-data; name="token"', b'form-data; name="retry"', b'form-data; name="expire"', b'form-data; name="message"', b'form-data; name="attachment"; filename="image.jpg"', ] # Proof parameter body parts, modulo image content, have correct values. assert list(contents.values())[:-1] == [ b"userkey2", b"appkey2", b"60", b"3600", b"\xe2\x9a\xbd Notification message \xe2\x9a\xbd", ] # Proof image has content. assert len(decoder.parts[-1].content) == 45628 assert outcome is True assert "Sending pushover notification to test" in caplog.text assert "Successfully sent pushover notification" in caplog.text
def load_services(services): for service in services: service_plugins[service] = {} service_config = cf.config('config:' + service) if service_config is None: logger.error("Service `%s' has no config section" % service) sys.exit(1) service_plugins[service]['config'] = service_config module = cf.g('config:' + service, 'module', service) # Load external service from file. modulefile_candidates = [] if module.endswith(".py"): # Add two candidates: a) Use the file as given and b) treat the file as relative to # the directory of the configuration file. That retains backward compatibility. modulefile_candidates.append(module) modulefile_candidates.append(os.path.join(cf.configuration_path, module)) # Load external service with module specification. elif '.' in module: logger.debug('Trying to load service "{}" from module "{}"'.format(service, module)) try: service_plugins[service]['module'] = load_module_by_name(module) logger.info('Successfully loaded service "{}" from module "{}"'.format(service, module)) continue except Exception as ex: logger.exception('Unable to load service "{}" from module "{}": {}'.format(service, module, ex)) # Load built-in service module. else: # Backward-compatibility patch for honoring the renaming of the `http.py` module. if module == "http": module = "http_urllib" logger.debug('Trying to load built-in service "{}" from "{}"'.format(service, module)) modulefile_candidates = [ resource_filename('mqttwarn.services', module + '.py') ] success = False for modulefile in modulefile_candidates: if not os.path.isfile(modulefile): continue logger.debug('Trying to load service "{}" from file "{}"'.format(service, modulefile)) try: service_plugins[service]['module'] = load_module_from_file(modulefile) logger.info('Successfully loaded service "{}"'.format(service)) success = True except Exception as ex: logger.exception('Unable to load service "{}" from file "{}": {}'.format(service, modulefile, ex)) if not success: logger.critical('Unable to load service "{}"'.format(service)) sys.exit(1)
def test_carbon_failure_invalid_configuration(srv, caplog): item = Item(target="test", addrs=["172.16.153.110", "foobar"]) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/carbon.py") outcome = module.plugin(srv, item) assert outcome is False assert "Configuration for target `carbon' is incorrect" in caplog.text
def test_alexa_notify_me_real_auth_failure(srv, caplog): module = load_module_from_file("mqttwarn/services/alexa-notify-me.py") accessCode = "myToken" item = Item(addrs=[accessCode], message="⚽ Notification message ⚽") with caplog.at_level(logging.DEBUG): outcome = module.plugin(srv, item) assert outcome is False assert "Sending to NotifyMe service" in caplog.text assert "Failed to send message to NotifyMe service" in caplog.text
def test_carbon_failure_empty_message(srv, caplog): item = Item(target="test", addrs=["172.16.153.110", 2003]) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/carbon.py") outcome = module.plugin(srv, item) assert outcome is False assert "target `carbon': cannot split string" in caplog.text
def test_pushover_failure_invalid_configuration(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={}, target="test", addrs=[None], ) with caplog.at_level(logging.DEBUG): outcome = module.plugin(srv, item) assert outcome is False assert "Invalid address configuration for target `test'" in caplog.text
def test_apprise_error(srv, caplog): with caplog.at_level(logging.DEBUG): mock_connection = mock.MagicMock() # Make the call to `notify` raise an exception. def error(*args, **kwargs): raise Exception("something failed") mock_connection.notify = error with mock.patch("apprise.Apprise", side_effect=[mock_connection], create=True) as mock_client: with mock.patch("apprise.AppriseAsset", create=True) as mock_asset: module = load_module_from_file( "mqttwarn/services/apprise_single.py") item = Item( config={ "baseuri": "mailtos://*****:*****@mail.example.org" }, target="test", addrs=["*****@*****.**", "*****@*****.**"], title="⚽ Message title ⚽", message="⚽ Notification message ⚽", ) outcome = module.plugin(srv, item) assert mock_client.mock_calls == [ mock.call(asset=mock.ANY), ] assert mock_connection.mock_calls == [ call.add( "mailtos://*****:*****@mail.example.org?to=foo%40example.org%2Cbar%40example.org" ), ] assert outcome is False assert ( "Sending notification to Apprise. target=test, addresses=['*****@*****.**', '*****@*****.**']" in caplog.messages) assert ( "Sending message using Apprise failed. target=test, error=something failed" in caplog.messages)
def test_pushover_failure_missing_credentials(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={}, target="test", addrs=[None, None], data={}, ) with caplog.at_level(logging.DEBUG): outcome = module.plugin(srv, item) assert outcome is False assert "No pushover credentials configured for target `test'" in caplog.text
def test_azure_iot_failure_wrong_qos(srv, caplog): item = Item( config={"iothubname": "acmehub", "qos": 999}, target="test", addrs=["device-id", "SharedAccessSignature sr=..."], message="⚽ Notification message ⚽", ) with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/azure_iot.py") outcome = module.plugin(srv, item) assert outcome is False assert "Only QoS 0 or 1 allowed for Azure IoT Hub, not '999'" in caplog.text
def test_pushover_success_with_imageurl_and_digest_authentication(srv, caplog): module = load_module_from_file("mqttwarn/services/pushover.py") item = Item( config={}, target="test", addrs=["userkey2", "appkey2"], message="⚽ Notification message ⚽", data={ "imageurl": "https://example.org/image", "auth": "digest", "user": "******", "password": "******", }, ) with caplog.at_level(logging.DEBUG): add_successful_mock_response() image = open("./assets/pushover.png", "rb").read() responses.add( responses.GET, "https://example.org/image", body=image, stream=True, status=200, ) outcome = module.plugin(srv, item) # Proof authentication on image request. # FIXME: Currently not possible because Digest auth will only work if # the server answers with 4xx. # assert ( # responses.calls[0].request.headers["Authorization"] == "Digest something" # ) # Check response status. assert responses.calls[1].response.status_code == 200 assert responses.calls[1].response.text == '{"status": 1}' assert outcome is True assert "Sending pushover notification to test" in caplog.text assert "Successfully sent pushover notification" in caplog.text
def test_apns_failure_invalid_config(mock_apns_payload, mock_apns, srv, caplog): with caplog.at_level(logging.DEBUG): module = load_module_from_file("mqttwarn/services/apns.py") item = Item( target="test", addrs=[None], message="⚽ Notification message ⚽", data={"apns_token": "foobar", "payload": "{}"}, ) outcome = module.plugin(srv, item) assert outcome is False assert "Incorrect service configuration" in caplog.text