From e483ffa13053010b4e3a44b695ee19b5303c4e6e Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sun, 20 Jul 2025 14:18:34 -0400 Subject: [PATCH] improved test coverage --- tests/test_plugin_slack.py | 146 +++++++++++++++++++++++++----------- tests/test_plugin_syslog.py | 42 ++++++++++- tox.ini | 2 +- 3 files changed, 142 insertions(+), 48 deletions(-) diff --git a/tests/test_plugin_slack.py b/tests/test_plugin_slack.py index 6c479f41..317d2d87 100644 --- a/tests/test_plugin_slack.py +++ b/tests/test_plugin_slack.py @@ -38,7 +38,7 @@ import pytest import requests from apprise import Apprise, AppriseAttachment, NotifyType -from apprise.plugins.slack import NotifySlack, SlackMode +from apprise.plugins.slack import NotifySlack logging.disable(logging.CRITICAL) @@ -565,48 +565,6 @@ def test_plugin_slack_oauth_access_token(mock_request): # We'll fail now because of an internal exception assert obj.send(body="test") is False - # --- Simulate failure to send file to channel (missing 'files') --- - mock_request.reset_mock() - mock_request.side_effect = [ - request, # chat.postMessage - mock.Mock(**{ # getUploadURLExternal - "content": dumps({ - "ok": True, - "upload_url": "https://files.slack.com/upload/v1/ABC123", - "file_id": "F123ABC456", - }), - "status_code": requests.codes.ok, - }), - mock.Mock(**{ # File upload - "content": b"OK - 123", - "status_code": requests.codes.ok, - }), - mock.Mock(**{ # completeUploadExternal returns invalid response - "content": dumps({ - "ok": True, - "files": [], # <== This triggers the unhit block - }), - "status_code": requests.codes.ok, - }), - ] - - path = os.path.join(TEST_VAR_DIR, "apprise-test.gif") - attach = AppriseAttachment(path) - - # Test via BOT Mode - obj.mode = SlackMode.BOT - - # This will now fail on 'response.get("files")' being empty - assert ( - obj.notify( - body="body", - title="title", - notify_type=NotifyType.INFO, - attach=attach, - ) - is False - ) - @mock.patch("requests.request") def test_plugin_slack_webhook_mode(mock_request): @@ -1040,3 +998,105 @@ def test_plugin_slack_multiple_thread_reply(mock_request): assert loads(mock_request.call_args_list[1][1]["data"]).get( "thread_ts" ) == str(thread_id_2) + + +@mock.patch("requests.request") +def test_plugin_slack_file_upload_success(mock_request): + """Test Slack BOT attachment upload success path.""" + + token = "xoxb-1234-1234-abc124" + path = os.path.join(TEST_VAR_DIR, "apprise-test.gif") + attach = AppriseAttachment(path) + + # Simulate all successful Slack API responses + mock_request.side_effect = [ + mock.Mock(**{ + "content": dumps({ + "ok": True, + "channel": "C123456", + }), + "status_code": requests.codes.ok, + }), + mock.Mock(**{ + "content": dumps({ + "ok": True, + "upload_url": "https://files.slack.com/upload/v1/ABC123", + "file_id": "F123ABC456", + }), + "status_code": requests.codes.ok, + }), + mock.Mock(**{ + "content": b"OK - 123", + "status_code": requests.codes.ok, + }), + mock.Mock(**{ + "content": dumps({ + "ok": True, + "files": [{"id": "F123ABC456", "title": "apprise-test"}], + }), + "status_code": requests.codes.ok, + }), + ] + + obj = NotifySlack(access_token=token, targets=["#general"]) + assert obj.notify( + body="Success path test", + title="Slack Upload OK", + notify_type=NotifyType.INFO, + attach=attach, + ) is True + + +@mock.patch("requests.request") +def test_plugin_slack_file_upload_fails_missing_files(mock_request): + """Test that file upload fails when 'files' is missing or empty.""" + + token = "xoxb-1234-1234-abc124" + path = os.path.join(TEST_VAR_DIR, "apprise-test.gif") + attach = AppriseAttachment(path) + + # Mock sequence: + # 1. chat.postMessage returns valid channel + # 2. files.getUploadURLExternal returns file_id and upload_url + # 3. Upload returns 'OK' + # 4. files.completeUploadExternal returns missing/empty 'files' + + mock_request.side_effect = [ + mock.Mock(**{ + "content": dumps({ + "ok": True, + "channel": "C555555", + }), + "status_code": requests.codes.ok, + }), + mock.Mock(**{ + "content": dumps({ + "ok": True, + "upload_url": "https://files.slack.com/upload/v1/X99999", + "file_id": "F999XYZ888", + }), + "status_code": requests.codes.ok, + }), + mock.Mock(**{ + "content": b"OK - 2048", + "status_code": requests.codes.ok, + }), + # <== This response will trigger the error condition + mock.Mock(**{ + "content": dumps({ + "ok": True, + "files": [], + }), + "status_code": requests.codes.ok, + }), + ] + + obj = NotifySlack(access_token=token, targets=["#fail-channel"]) + result = obj.notify( + body="This should trigger a failed file upload", + title="Trigger failure", + notify_type=NotifyType.INFO, + attach=attach, + ) + + assert result is False diff --git a/tests/test_plugin_syslog.py b/tests/test_plugin_syslog.py index b68da019..fbb399ab 100644 --- a/tests/test_plugin_syslog.py +++ b/tests/test_plugin_syslog.py @@ -35,11 +35,45 @@ import pytest import apprise -logging.disable(logging.CRITICAL) +try: + import syslog -# Skip tests when Python environment does not provide the `syslog` package. -if "syslog" not in sys.modules: - pytest.skip("Skipping syslog based tests", allow_module_level=True) +except ImportError: + # Shim so that test cases can run in environments that + # do not have syslog + import types + syslog = types.SimpleNamespace( + LOG_PID=0x01, + LOG_PERROR=0x02, + LOG_INFO=6, + LOG_NOTICE=5, + LOG_CRIT=2, + LOG_WARNING=4, + LOG_KERN=0, + LOG_USER=1, + LOG_MAIL=2, + LOG_DAEMON=3, + LOG_AUTH=4, + LOG_SYSLOG=5, + LOG_LPR=6, + LOG_NEWS=7, + LOG_UUCP=8, + LOG_CRON=9, + LOG_LOCAL0=16, + LOG_LOCAL1=17, + LOG_LOCAL2=18, + LOG_LOCAL3=19, + LOG_LOCAL4=20, + LOG_LOCAL5=21, + LOG_LOCAL6=22, + LOG_LOCAL7=23, + openlog=lambda *a, **kw: None, + syslog=lambda *a, **kw: None, + ) + sys.modules["syslog"] = syslog + + +logging.disable(logging.CRITICAL) from apprise.plugins.syslog import NotifySyslog # noqa E402 diff --git a/tox.ini b/tox.ini index 7eb8c5b6..78bca548 100644 --- a/tox.ini +++ b/tox.ini @@ -97,7 +97,7 @@ commands = find . -type f -name "*.pyc" -delete find . -type f -name "*.pyo" -delete find . -type d -name "__pycache__" -delete - rm -rf BUILD SOURCES SRPMS BUILDROOT .ruff_cache .coverage-reports .coverage coverage.xml dist build apprise.egg-info .mypy_cache .pytest_cache + rm -rf BUILD SOURCES SRPMS BUILDROOT .cache .ruff_cache .coverage-reports .coverage coverage.xml dist build apprise.egg-info .mypy_cache .pytest_cache [testenv:i18n] description = Extract and update .pot/.po files for translation