smpp dependency cleanup and test case prep

pull/1354/head
Chris Caron 2025-06-29 15:26:13 -04:00
parent 875c8349fe
commit 52a53d6f33
3 changed files with 63 additions and 60 deletions

View File

@ -14,3 +14,6 @@ paho-mqtt != 2.0.*
# Pretty Good Privacy (PGP) Provides mailto:// and deltachat:// support # Pretty Good Privacy (PGP) Provides mailto:// and deltachat:// support
PGPy PGPy
# Provides smpp:// support
python-smpp

View File

@ -28,9 +28,18 @@
from itertools import chain from itertools import chain
import smpplib try:
import smpplib.consts import smpplib
import smpplib.gsm import smpplib.consts
import smpplib.gsm
# We're good to go!
NOTIFY_SMPP_ENABLED = True
except ImportError:
# cryptography is required in order for this package to work
NOTIFY_SMPP_ENABLED = False
from .base import NotifyBase from .base import NotifyBase
from ..common import NotifyType from ..common import NotifyType
@ -38,18 +47,29 @@ from ..locale import gettext_lazy as _
from ..utils.parse import is_phone_no, parse_phone_no from ..utils.parse import is_phone_no, parse_phone_no
class NotifySmpp(NotifyBase): class NotifySMPP(NotifyBase):
""" """
A wrapper for SMPP Notifications A wrapper for SMPP Notifications
""" """
# Set our global enabled flag
enabled = NOTIFY_SMPP_ENABLED
requirements = {
# Define our required packaging in order to work
'packages_required': 'python-snpp'
}
# The default descriptive name associated with the Notification # The default descriptive name associated with the Notification
service_name = _('SMPP') service_name = _('SMPP')
# The services URL # The services URL
service_url = 'https://smpp.org/' service_url = 'https://smpp.org/'
# The default protocol
protocol = 'smpp' protocol = 'smpp'
# The default secure protocol
secure_protocol = 'smpps' secure_protocol = 'smpps'
# A URL that takes you to the setup/help of the specific protocol # A URL that takes you to the setup/help of the specific protocol
@ -175,7 +195,7 @@ class NotifySmpp(NotifyBase):
port=self.port, port=self.port,
source=self.source, source=self.source,
targets='/'.join( targets='/'.join(
[NotifySmpp.quote(t, safe='') [NotifySMPP.quote(t, safe='')
for t in chain(self.targets, self._invalid_targets)]), for t in chain(self.targets, self._invalid_targets)]),
params=self.urlencode(params), params=self.urlencode(params),
) )
@ -249,27 +269,27 @@ class NotifySmpp(NotifyBase):
# We're done early as we couldn't load the results # We're done early as we couldn't load the results
return results return results
results['targets'] = NotifySmpp.split_path(results['fullpath']) results['targets'] = NotifySMPP.split_path(results['fullpath'])
# Support the 'to' variable so that we can support targets this way too # Support the 'to' variable so that we can support targets this way too
# 'to' makes it easier to use yaml configuration # 'to' makes it easier to use yaml configuration
if 'to' in results['qsd'] and len(results['qsd']['to']): if 'to' in results['qsd'] and len(results['qsd']['to']):
results['targets'] += \ results['targets'] += \
NotifySmpp.parse_phone_no(results['qsd']['to']) NotifySMPP.parse_phone_no(results['qsd']['to'])
# store any additional payload extras defined # store any additional payload extras defined
results['payload'] = {NotifySmpp.unquote(x): NotifySmpp.unquote(y) results['payload'] = {NotifySMPP.unquote(x): NotifySMPP.unquote(y)
for x, y in results['qsd:'].items()} for x, y in results['qsd:'].items()}
# Add our GET parameters in the event the user wants to pass them # Add our GET parameters in the event the user wants to pass them
results['params'] = {NotifySmpp.unquote(x): NotifySmpp.unquote(y) results['params'] = {NotifySMPP.unquote(x): NotifySMPP.unquote(y)
for x, y in results['qsd-'].items()} for x, y in results['qsd-'].items()}
# Support the 'from' and 'source' variable so that we can support # Support the 'from' and 'source' variable so that we can support
# targets this way too. # targets this way too.
# 'from' makes it easier to use yaml configuration # 'from' makes it easier to use yaml configuration
if 'from' in results['qsd'] and len(results['qsd']['from']): if 'from' in results['qsd'] and len(results['qsd']['from']):
results['source'] = NotifySmpp.unquote(results['qsd']['from']) results['source'] = NotifySMPP.unquote(results['qsd']['from'])
elif results['targets']: elif results['targets']:
# from phone number is the first entry in the list otherwise # from phone number is the first entry in the list otherwise
results['source'] = results['targets'].pop(0) results['source'] = results['targets'].pop(0)

View File

@ -27,13 +27,11 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
import logging import logging
from json import dumps import sys
from unittest import mock
import pytest import pytest
import requests from apprise import Apprise
from apprise.plugins.smpp import NotifySMPP
from apprise.plugins.smpp import NotifySmpp
from helpers import AppriseURLTester from helpers import AppriseURLTester
logging.disable(logging.CRITICAL) logging.disable(logging.CRITICAL)
@ -66,32 +64,32 @@ apprise_url_tests = (
}), }),
('smpp://user:pass@host:port/{}/{}'.format('1' * 10, 'a' * 32), { ('smpp://user:pass@host:port/{}/{}'.format('1' * 10, 'a' * 32), {
# valid everything but target numbers # valid everything but target numbers
'instance': NotifySmpp, 'instance': NotifySMPP,
# We have no one to notify # We have no one to notify
'notify_response': False, 'notify_response': False,
}), }),
('smpp://user:pass@host:port/{}'.format('1' * 10), { ('smpp://user:pass@host:port/{}'.format('1' * 10), {
# everything valid # everything valid
'instance': NotifySmpp, 'instance': NotifySMPP,
# We have no one to notify # We have no one to notify
'notify_response': False, 'notify_response': False,
}), }),
('smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10), { ('smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10), {
'instance': NotifySmpp, 'instance': NotifySMPP,
}), }),
('smpp://_?&from={}&to={},{}'.format( ('smpp://_?&from={}&to={},{}'.format(
'1' * 10, '1' * 10, '1' * 10), { '1' * 10, '1' * 10, '1' * 10), {
# use get args to accomplish the same thing # use get args to accomplish the same thing
'instance': NotifySmpp, 'instance': NotifySMPP,
}), }),
('smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10), { ('smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10), {
'instance': NotifySmpp, 'instance': NotifySMPP,
# throw a bizarre code forcing us to fail to look it up # throw a bizarre code forcing us to fail to look it up
'response': False, 'response': False,
'requests_response_code': 999, 'requests_response_code': 999,
}), }),
('smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10), { ('smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10), {
'instance': NotifySmpp, 'instance': NotifySMPP,
# Throws a series of connection and transfer exceptions when this flag # Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracefully handle them # is set and tests that we gracefully handle them
'test_requests_exceptions': True, 'test_requests_exceptions': True,
@ -99,46 +97,28 @@ apprise_url_tests = (
) )
@pytest.mark.skipif(
'python-smpp' in sys.modules,
reason="Requires that python-smpp NOT be installed")
def test_plugin_fcm_cryptography_import_error():
"""
NotifySimplePush() python-smpp loading failure
"""
# Attempt to instantiate our object
obj = Apprise.instantiate(
'smpp://user:pass@host:port/{}/{}'.format('1' * 10, '1' * 10))
# It's not possible because our cryptography depedancy is missing
assert obj is None
@pytest.mark.skipif(
'python-smpp' not in sys.modules, reason="Requires python-smpp")
def test_plugin_smpp_urls(): def test_plugin_smpp_urls():
""" """
NotifySmpp() Apprise URLs NotifySMPP() Apprise URLs
""" """
# Run our general tests # Run our general tests
AppriseURLTester(tests=apprise_url_tests).run_all() AppriseURLTester(tests=apprise_url_tests).run_all()
@mock.patch('requests.post')
def test_plugin_smpp_edge_cases(mock_post):
"""
NotifySmpp() Edge Cases
"""
# Prepare our response
response = requests.Request()
response.status_code = requests.codes.ok
# Prepare Mock
mock_post.return_value = response
# Initialize some generic (but valid) apikeys
apikey = 'b' * 32
source = '+1 (555) 123-3456'
# No apikey specified
with pytest.raises(TypeError):
NotifySmpp(source=source)
# a error response
response.status_code = 400
response.content = dumps({
'code': 21211,
'message': "The 'To' number +1234567 is not a valid phone number.",
})
mock_post.return_value = response
# Initialize our object
obj = NotifySmpp(source=source)
# We will fail with the above error code
assert obj.notify('title', 'body', 'info') is False