diff --git a/apprise/plugins/webexteams.py b/apprise/plugins/webexteams.py index 2c4b829c..35c460ee 100644 --- a/apprise/plugins/webexteams.py +++ b/apprise/plugins/webexteams.py @@ -122,6 +122,15 @@ class NotifyWebexTeams(NotifyBase): }, ) + template_args = dict( + NotifyBase.template_args, + **{ + "token": { + "alias_of": "token", + }, + }, + ) + def __init__(self, token, **kwargs): """Initialize Webex Teams Object.""" super().__init__(**kwargs) diff --git a/bin/README.md b/bin/README.md index c839b817..e64d6e9b 100644 --- a/bin/README.md +++ b/bin/README.md @@ -139,7 +139,8 @@ docker-compose run --rm test.py312 bash ``` Once you've entered one of these environments, you can leverage the following command to work with: -1. `bin/test.sh`: runs the full test suite (same as `tox -e qa`) +1. `bin/test.sh`: runs the full test suite (same as `tox -e qa`) but without coveage +1. `bin/checkdone.sh`: runs the full test suite (same as `tox -e qa`) 1. `bin/apprise`: launches the Apprise CLI using the local build (same as `tox -e apprise`) 1. `ruff check . --fix`: auto-formats the codebase (same as `tox -e format`) 1. `ruff check .`: performs lint-only validation (same as `tox -e lint`) diff --git a/bin/checkdone.sh b/bin/checkdone.sh new file mode 100755 index 00000000..c30c12dc --- /dev/null +++ b/bin/checkdone.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# -*- coding: utf-8 -*- +# BSD 2-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2025, Chris Caron +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# Absolute path to this script, e.g. /home/user/bin/foo.sh +SCRIPT=$(readlink -f "$0") + +# Absolute path this script is in, thus /home/user/bin +SCRIPTPATH=$(dirname "$SCRIPT") + +PYTHONPATH="" + +FOUNDROOT=1 +if [ -f "$(dirname $SCRIPTPATH)/pyproject.toml" ]; then + pushd "$(dirname $SCRIPTPATH)" &>/dev/null + FOUNDROOT=$? + PYTHONPATH="$(dirname $SCRIPTPATH)" + +elif [ -f "$SCRIPTPATH/pyproject.toml" ]; then + pushd "$SCRIPTPATH" &>/dev/null + FOUNDROOT=$? + PYTHONPATH="$SCRIPTPATH" +fi + +if [ $FOUNDROOT -ne 0 ]; then + echo "Error: Could not locate Apprise pyproject.toml file." + exit 1 +fi + +# Tidy previous reports (if present) +[ -d .coverage-reports ] && rm -rf .coverage-reports + +# This is a useful tool for checking for any lint errors and additionally +# checking the overall coverage. +which ruff &>/dev/null +[ $? -ne 0 ] && \ + echo "Missing ruff; make sure it is installed:" && \ + echo " > pip install ruff" && \ + exit 1 + +which coverage &>/dev/null +[ $? -ne 0 ] && \ + echo "Missing coverage; make sure it is installed:" && + echo " > pip install pytest-cov coverage" && \ + exit 1 + +echo "Performing PEP8 check..." +LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH ruff check +if [ $? -ne 0 ]; then + echo "PEP8 check failed" + exit 1 +fi +echo "PEP8 check succeeded; no errors found! :)" +echo + +# Run our unit test coverage check +echo "Running test coverage check..." +pushd $PYTHONPATH &>/dev/null +if [ ! -z "$@" ]; then + LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage run -m pytest -vv -k "$@" + RET=$? + +else + LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage run -m pytest -vv + RET=$? +fi + +if [ $RET -ne 0 ]; then + echo "Tests failed." + exit 1 +fi + +# Build our report +LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage combine + +# Prepare XML Reference +LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage xml + +# Print our report +LANG=C.UTF-8 PYTHONPATH=$PYTHONPATH coverage report --show-missing diff --git a/test/test_plugin_simplepush.py b/test/test_plugin_simplepush.py deleted file mode 100644 index 53b4f2e9..00000000 --- a/test/test_plugin_simplepush.py +++ /dev/null @@ -1,178 +0,0 @@ -# BSD 2-Clause License -# -# Apprise - Push Notification Library. -# Copyright (c) 2025, Chris Caron -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -import json - -# Disable logging for a cleaner testing output -import logging -import sys -from unittest import mock - -from helpers import AppriseURLTester -import pytest -import requests - -from apprise import Apprise -from apprise.plugins.simplepush import NotifySimplePush - -logging.disable(logging.CRITICAL) - -# Our Testing URLs -apprise_url_tests = ( - ("spush://", { - # No api key - "instance": TypeError, - }), - ("spush://{}".format("A" * 14), { - # API Key specified however expected server response - # didn't have 'OK' in JSON response - "instance": NotifySimplePush, - # Expected notify() response - "notify_response": False, - }), - ("spush://{}".format("Y" * 14), { - # API Key valid and expected response was valid - "instance": NotifySimplePush, - # Set our response to OK - "requests_response_text": { - "status": "OK", - }, - - # Our expected url(privacy=True) startswith() response: - "privacy_url": "spush://Y...Y/", - }), - ("spush://{}?event=Not%20So%20Good".format("X" * 14), { - # API Key valid and expected response was valid - "instance": NotifySimplePush, - # Set our response to something that is not okay - "requests_response_text": { - "status": "NOT-OK", - }, - # Expected notify() response - "notify_response": False, - }), - ("spush://salt:pass@{}".format("X" * 14), { - # Now we'll test encrypted messages with new salt - "instance": NotifySimplePush, - # Set our response to OK - "requests_response_text": { - "status": "OK", - }, - - # Our expected url(privacy=True) startswith() response: - "privacy_url": "spush://****:****@X...X/", - }), - ("spush://{}".format("Y" * 14), { - "instance": NotifySimplePush, - # throw a bizzare code forcing us to fail to look it up - "response": False, - "requests_response_code": 999, - # Set a failing message too - "requests_response_text": { - "status": "BadRequest", - "message": "Title or message too long", - }, - }), - ("spush://{}".format("Z" * 14), { - "instance": NotifySimplePush, - # Throws a series of connection and transfer exceptions when this flag - # is set and tests that we gracfully handle them - "test_requests_exceptions": True, - }), -) - - -@pytest.mark.skipif( - "cryptography" not in sys.modules, reason="Requires cryptography") -def test_plugin_simplepush_urls(): - """ - NotifySimplePush() Apprise URLs - - """ - - # Run our general tests - AppriseURLTester(tests=apprise_url_tests).run_all() - - -@pytest.mark.skipif( - "cryptography" in sys.modules, - reason="Requires that cryptography NOT be installed") -def test_plugin_simpepush_cryptography_import_error(): - """ - NotifySimplePush() Cryptography loading failure - """ - - # Attempt to instantiate our object - obj = Apprise.instantiate("spush://{}".format("Y" * 14)) - - # It's not possible because our cryptography depedancy is missing - assert obj is None - - -@pytest.mark.skipif( - "cryptography" not in sys.modules, reason="Requires cryptography") -def test_plugin_simplepush_edge_cases(): - """ - NotifySimplePush() Edge Cases - - """ - - # No token - with pytest.raises(TypeError): - NotifySimplePush(apikey=None) - - with pytest.raises(TypeError): - NotifySimplePush(apikey=" ") - - # Bad event - with pytest.raises(TypeError): - NotifySimplePush(apikey="abc", event=object) - - with pytest.raises(TypeError): - NotifySimplePush(apikey="abc", event=" ") - - -@pytest.mark.skipif( - "cryptography" not in sys.modules, reason="Requires cryptography") -@mock.patch("requests.post") -def test_plugin_simplepush_general(mock_post): - """ - NotifySimplePush() General Tests - """ - - # Prepare a good response - response = mock.Mock() - response.content = json.dumps({ - "status": "OK", - }) - response.status_code = requests.codes.ok - mock_post.return_value = response - - obj = Apprise.instantiate("spush://{}".format("Y" * 14)) - - # Verify our content works as expected - assert obj.notify(title="test", body="test") is True diff --git a/test/test_plugin_webex_teams.py b/test/test_plugin_webex_teams.py deleted file mode 100644 index 63fe5afa..00000000 --- a/test/test_plugin_webex_teams.py +++ /dev/null @@ -1,115 +0,0 @@ -# BSD 2-Clause License -# -# Apprise - Push Notification Library. -# Copyright (c) 2025, Chris Caron -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -# Disable logging for a cleaner testing output -import logging - -from helpers import AppriseURLTester -import requests - -from apprise.plugins.webexteams import NotifyWebexTeams - -logging.disable(logging.CRITICAL) - -# Our Testing URLs -apprise_url_tests = ( - ("wxteams://", { - # Teams Token missing - "instance": TypeError, - }), - ("wxteams://:@/", { - # We don't have strict host checking on for wxteams, so this URL - # actually becomes parseable and :@ becomes a hostname. - # The below errors because a second token wasn't found - "instance": TypeError, - }), - ("wxteams://{}".format("a" * 80), { - # token provided - we're good - "instance": NotifyWebexTeams, - - # Our expected url(privacy=True) startswith() response: - "privacy_url": "wxteams://a...a/", - }), - ("wxteams://?token={}".format("a" * 80), { - # token provided - we're good - "instance": NotifyWebexTeams, - - # Our expected url(privacy=True) startswith() response: - "privacy_url": "wxteams://a...a/", - }), - ("webex://{}".format("a" * 140), { - # token provided - we're good - "instance": NotifyWebexTeams, - - # Our expected url(privacy=True) startswith() response: - "privacy_url": "wxteams://a...a/", - }), - # Support Native URLs - ("https://api.ciscospark.com/v1/webhooks/incoming/{}".format("a" * 80), { - # token provided - we're good - "instance": NotifyWebexTeams, - }), - # Support New Native URLs - ("https://webexapis.com/v1/webhooks/incoming/{}".format("a" * 100), { - # token provided - we're good - "instance": NotifyWebexTeams, - }), - # Support Native URLs with arguments - ("https://api.ciscospark.com/v1/webhooks/incoming/{}?format=text".format( - "a" * 80), { - # token provided - we're good - "instance": NotifyWebexTeams, - }), - ("wxteams://{}".format("a" * 80), { - "instance": NotifyWebexTeams, - # force a failure - "response": False, - "requests_response_code": requests.codes.internal_server_error, - }), - ("wxteams://{}".format("a" * 80), { - "instance": NotifyWebexTeams, - # throw a bizzare code forcing us to fail to look it up - "response": False, - "requests_response_code": 999, - }), - ("wxteams://{}".format("a" * 80), { - "instance": NotifyWebexTeams, - # Throws a series of connection and transfer exceptions when this flag - # is set and tests that we gracfully handle them - "test_requests_exceptions": True, - }), -) - - -def test_plugin_webex_teams_urls(): - """ - NotifyWebexTeams() Apprise URLs - - """ - - # Run our general tests - AppriseURLTester(tests=apprise_url_tests).run_all() diff --git a/tests/test_plugin_simplepush.py b/tests/test_plugin_simplepush.py index 56330425..4d55333d 100644 --- a/tests/test_plugin_simplepush.py +++ b/tests/test_plugin_simplepush.py @@ -137,10 +137,11 @@ def test_plugin_simplepush_urls(): @pytest.mark.skipif( "cryptography" in sys.modules, - reason="Requires that cryptography NOT be installed", -) -def test_plugin_fcm_cryptography_import_error(): - """NotifySimplePush() Cryptography loading failure.""" + reason="Requires that cryptography NOT be installed") +def test_plugin_simpepush_cryptography_import_error(): + """ + NotifySimplePush() Cryptography loading failure + """ # Attempt to instantiate our object obj = Apprise.instantiate("spush://{}".format("Y" * 14)) @@ -150,10 +151,12 @@ def test_plugin_fcm_cryptography_import_error(): @pytest.mark.skipif( - "cryptography" not in sys.modules, reason="Requires cryptography" -) + "cryptography" not in sys.modules, reason="Requires cryptography") def test_plugin_simplepush_edge_cases(): - """NotifySimplePush() Edge Cases.""" + """ + NotifySimplePush() Edge Cases + + """ # No token with pytest.raises(TypeError): diff --git a/tests/test_plugin_webex_teams.py b/tests/test_plugin_webex_teams.py index 52281276..fc00789e 100644 --- a/tests/test_plugin_webex_teams.py +++ b/tests/test_plugin_webex_teams.py @@ -62,6 +62,15 @@ apprise_url_tests = ( "privacy_url": "wxteams://a...a/", }, ), + ( + "wxteams://?token={}".format("a" * 80), + { + # token provided - we're good + "instance": NotifyWebexTeams, + # Our expected url(privacy=True) startswith() response: + "privacy_url": "wxteams://a...a/", + }, + ), ( "webex://{}".format("a" * 140), {