Ejemplo n.º 1
0
 def _make_esme(self):
     self.esme_callbacks = EsmeCallbacks(
         connect=lambda: None,
         disconnect=lambda: None,
         submit_sm_resp=self.transport.submit_sm_resp,
         delivery_report=self.transport.delivery_report,
         deliver_sm=lambda: None)
     self.esme = EsmeTransceiver(self.transport.get_static_config(),
                                 self.transport.get_smpp_bind_params(),
                                 self.transport.redis, self.esme_callbacks)
     self.esme.sent_pdus = []
     self.esme.send_pdu = self.esme.sent_pdus.append
     self.esme.state = 'BOUND_TRX'
Ejemplo n.º 2
0
 def _make_esme(self):
     self.esme_callbacks = EsmeCallbacks(
         connect=lambda: None, disconnect=lambda: None,
         submit_sm_resp=self.transport.submit_sm_resp,
         delivery_report=self.transport.delivery_report,
         deliver_sm=lambda: None)
     self.esme = EsmeTransceiver(
         self.transport.get_static_config(),
         self.transport.get_smpp_bind_params(),
         self.transport.redis, self.esme_callbacks)
     self.esme.sent_pdus = []
     self.esme.send_pdu = self.esme.sent_pdus.append
     self.esme.state = 'BOUND_TRX'
Ejemplo n.º 3
0
class TestSmppTransport(VumiTestCase):

    @inlineCallbacks
    def setUp(self):
        config = {
            "system_id": "vumitest-vumitest-vumitest",
            "twisted_endpoint": "tcp:host=127.0.0.1:port=0",
            "password": "******",
            "smpp_bind_timeout": 12,
            "smpp_enquire_link_interval": 123,
            "third_party_id_expiry": 3600,  # just 1 hour
        }

        # hack a lot of transport setup
        self.tx_helper = self.add_helper(TransportHelper(SmppTransport))
        self.transport = yield self.tx_helper.get_transport(
            config, start=False)
        self.transport.esme_client = None
        yield self.transport.startWorker()

        self._make_esme()
        self.transport.esme_client = self.esme
        self.transport.esme_connected(self.esme)

    def _make_esme(self):
        self.esme_callbacks = EsmeCallbacks(
            connect=lambda: None, disconnect=lambda: None,
            submit_sm_resp=self.transport.submit_sm_resp,
            delivery_report=self.transport.delivery_report,
            deliver_sm=lambda: None)
        self.esme = EsmeTransceiver(
            self.transport.get_static_config(),
            self.transport.get_smpp_bind_params(),
            self.transport.redis, self.esme_callbacks)
        self.esme.sent_pdus = []
        self.esme.send_pdu = self.esme.sent_pdus.append
        self.esme.state = 'BOUND_TRX'

    def assert_sent_contents(self, expected):
        pdu_contents = [p.obj['body']['mandatory_parameters']['short_message']
                        for p in self.esme.sent_pdus]
        self.assertEqual(expected, pdu_contents)

    @inlineCallbacks
    def test_message_persistence(self):
        # A simple test of set -> get -> delete for redis message persistence
        message1 = self.tx_helper.make_outbound("hello world")
        original_json = message1.to_json()
        yield self.transport.r_set_message(message1)
        retrieved_json = yield self.transport.r_get_message_json(
            message1['message_id'])
        self.assertEqual(original_json, retrieved_json)
        retrieved_message = yield self.transport.r_get_message(
            message1['message_id'])
        self.assertEqual(retrieved_message, message1)
        self.assertTrue((yield self.transport.r_delete_message(
                    message1['message_id'])))
        self.assertEqual((yield self.transport.r_get_message_json(
                    message1['message_id'])), None)
        self.assertEqual((yield self.transport.r_get_message(
                    message1['message_id'])), None)

    @inlineCallbacks
    def test_message_persistence_expiry(self):
        message = self.tx_helper.make_outbound("hello world")
        yield self.transport.r_set_message(message)

        # check that the expiry is set
        message_key = self.transport.r_message_key(message['message_id'])
        config = self.transport.get_static_config()
        ttl = yield self.transport.redis.ttl(message_key)
        self.assertTrue(0 < ttl <= config.submit_sm_expiry)

    @inlineCallbacks
    def test_redis_third_party_id_persistence(self):
        # Testing: set -> get -> delete, for redis third party id mapping
        self.assertEqual(
            self.transport.get_static_config().third_party_id_expiry, 3600)
        our_id = "blergh34534545433454354"
        their_id = "omghesvomitingnumbers"
        yield self.transport.r_set_id_for_third_party_id(their_id, our_id)
        retrieved_our_id = (
            yield self.transport.r_get_id_for_third_party_id(their_id))
        self.assertEqual(our_id, retrieved_our_id)
        self.assertTrue((
                yield self.transport.r_delete_for_third_party_id(their_id)))
        self.assertEqual(None, (
                yield self.transport.r_get_id_for_third_party_id(their_id)))

    @inlineCallbacks
    def test_out_of_order_responses(self):
        # Sequence numbers are hardcoded, assuming we start fresh from 0.
        yield self.tx_helper.make_dispatch_outbound("msg 1", message_id='444')
        response1 = SubmitSMResp(1, "3rd_party_id_1")

        yield self.tx_helper.make_dispatch_outbound("msg 2", message_id='445')
        response2 = SubmitSMResp(2, "3rd_party_id_2")

        self.assert_sent_contents(["msg 1", "msg 2"])
        # respond out of order - just to keep things interesting
        yield self.esme.handle_data(response2.get_bin())
        yield self.esme.handle_data(response1.get_bin())

        [ack1, ack2] = self.tx_helper.get_dispatched_events()
        self.assertEqual(ack1['user_message_id'], '445')
        self.assertEqual(ack1['sent_message_id'], '3rd_party_id_2')
        self.assertEqual(ack2['user_message_id'], '444')
        self.assertEqual(ack2['sent_message_id'], '3rd_party_id_1')

    @inlineCallbacks
    def test_failed_submit(self):
        message = yield self.tx_helper.make_dispatch_outbound(
            "message", message_id='446')
        response = SubmitSMResp(
            1, "3rd_party_id_3", command_status="ESME_RSUBMITFAIL")
        yield self.esme.handle_data(response.get_bin())

        self.assert_sent_contents(["message"])
        # There should be a nack
        [nack] = yield self.tx_helper.wait_for_dispatched_events(1)
        self.assertEqual(nack['user_message_id'], message['message_id'])
        self.assertEqual(nack['nack_reason'], 'ESME_RSUBMITFAIL')

        [failure] = yield self.tx_helper.get_dispatched_failures()
        self.assertEqual(failure['reason'], 'ESME_RSUBMITFAIL')

    @inlineCallbacks
    def test_failed_submit_with_no_reason(self):
        message = yield self.tx_helper.make_dispatch_outbound(
            "message", message_id='446')
        # Equivalent of SubmitSMResp(1, "3rd_party_id_3", command_status='XXX')
        # but with a bad command_status (pdu_builder can't produce binary with
        # command_statuses it doesn't understand). Use
        # smpp.pdu.unpack(response_bin) to get a PDU object:
        response_hex = ("0000001f80000004"
                        "0000ffff"  # unknown command status
                        "000000013372645f70617274795f69645f3300")
        yield self.esme.handle_data(binascii.a2b_hex(response_hex))

        self.assert_sent_contents(["message"])
        # There should be a nack
        [nack] = yield self.tx_helper.wait_for_dispatched_events(1)
        self.assertEqual(nack['user_message_id'], message['message_id'])
        self.assertEqual(nack['nack_reason'], 'Unspecified')

        [failure] = yield self.tx_helper.get_dispatched_failures()
        self.assertEqual(failure['reason'], 'Unspecified')

    @inlineCallbacks
    def test_delivery_report_for_unknown_message(self):
        dr = ("id:123 sub:... dlvrd:... submit date:200101010030"
              " done date:200101020030 stat:DELIVRD err:... text:Meep")
        deliver = DeliverSM(1, short_message=dr)
        with LogCatcher(message="Failed to retrieve message id") as lc:
            yield self.esme.handle_data(deliver.get_bin())
            [warning] = lc.logs
            self.assertEqual(warning['message'],
                             ("Failed to retrieve message id for delivery "
                              "report. Delivery report from %s "
                              "discarded." % self.tx_helper.transport_name,))

    @inlineCallbacks
    def test_throttled_submit_ESME_RTHROTTLED(self):
        clock = Clock()
        self.transport.callLater = clock.callLater

        def assert_throttled_status(throttled, messages, acks):
            self.assertEqual(self.transport.throttled, throttled)
            self.assert_sent_contents(messages)
            self.assertEqual(acks, [
                (m['user_message_id'], m['sent_message_id'])
                for m in self.tx_helper.get_dispatched_events()])
            self.assertEqual([], self.tx_helper.get_dispatched_failures())

        assert_throttled_status(False, [], [])

        yield self.tx_helper.make_dispatch_outbound(
            "Heimlich", message_id="447")
        response = SubmitSMResp(1, "3rd_party_id_4",
                                command_status="ESME_RTHROTTLED")
        yield self.esme.handle_data(response.get_bin())

        assert_throttled_status(True, ["Heimlich"], [])
        # Still waiting to resend
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich"], [])
        # Don't wait for this, because it won't be processed until later.
        self.tx_helper.make_dispatch_outbound("Other", message_id="448")
        assert_throttled_status(True, ["Heimlich"], [])
        # Resent
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich", "Heimlich"], [])
        # And acknowledged by the other side
        yield self.esme.handle_data(SubmitSMResp(2, "3rd_party_5").get_bin())
        yield self.tx_helper.kick_delivery()
        yield self.esme.handle_data(SubmitSMResp(3, "3rd_party_6").get_bin())
        assert_throttled_status(
            False, ["Heimlich", "Heimlich", "Other"],
            [('447', '3rd_party_5'), ('448', '3rd_party_6')])

    @inlineCallbacks
    def test_throttled_submit_ESME_RMSGQFUL(self):
        clock = Clock()
        self.transport.callLater = clock.callLater

        def assert_throttled_status(throttled, messages, acks):
            self.assertEqual(self.transport.throttled, throttled)
            self.assert_sent_contents(messages)
            self.assertEqual(acks, [
                (m['user_message_id'], m['sent_message_id'])
                for m in self.tx_helper.get_dispatched_events()])
            self.assertEqual([], self.tx_helper.get_dispatched_failures())

        assert_throttled_status(False, [], [])

        yield self.tx_helper.make_dispatch_outbound(
            "Heimlich", message_id="447")
        response = SubmitSMResp(1, "3rd_party_id_4",
                                command_status="ESME_RMSGQFUL")
        yield self.esme.handle_data(response.get_bin())

        assert_throttled_status(True, ["Heimlich"], [])
        # Still waiting to resend
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich"], [])
        # Don't wait for this, because it won't be processed until later.
        self.tx_helper.make_dispatch_outbound("Other", message_id="448")
        assert_throttled_status(True, ["Heimlich"], [])
        # Resent
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich", "Heimlich"], [])
        # And acknowledged by the other side
        yield self.esme.handle_data(SubmitSMResp(2, "3rd_party_5").get_bin())
        yield self.tx_helper.kick_delivery()
        yield self.esme.handle_data(SubmitSMResp(3, "3rd_party_6").get_bin())
        assert_throttled_status(
            False, ["Heimlich", "Heimlich", "Other"],
            [('447', '3rd_party_5'), ('448', '3rd_party_6')])

    @inlineCallbacks
    def test_reconnect(self):
        connector = self.transport.connectors[self.transport.transport_name]
        self.assertFalse(connector._consumers['outbound'].paused)
        yield self.transport.esme_disconnected()
        self.assertTrue(connector._consumers['outbound'].paused)
        yield self.transport.esme_disconnected()
        self.assertTrue(connector._consumers['outbound'].paused)

        yield self.transport.esme_connected(self.esme)
        self.assertFalse(connector._consumers['outbound'].paused)
        yield self.transport.esme_connected(self.esme)
        self.assertFalse(connector._consumers['outbound'].paused)
Ejemplo n.º 4
0
 def __init__(self, *args, **kwargs):
     EsmeTransceiver.__init__(self, *args, **kwargs)
     self.setup_fake()
Ejemplo n.º 5
0
class TestSmppTransport(VumiTestCase):
    @inlineCallbacks
    def setUp(self):
        config = {
            "system_id": "vumitest-vumitest-vumitest",
            "twisted_endpoint": "tcp:host=127.0.0.1:port=0",
            "password": "******",
            "smpp_bind_timeout": 12,
            "smpp_enquire_link_interval": 123,
            "third_party_id_expiry": 3600,  # just 1 hour
        }

        # hack a lot of transport setup
        self.tx_helper = self.add_helper(TransportHelper(SmppTransport))
        self.transport = yield self.tx_helper.get_transport(config,
                                                            start=False)
        self.transport.esme_client = None
        yield self.transport.startWorker()

        self._make_esme()
        self.transport.esme_client = self.esme
        self.transport.esme_connected(self.esme)

    def _make_esme(self):
        self.esme_callbacks = EsmeCallbacks(
            connect=lambda: None,
            disconnect=lambda: None,
            submit_sm_resp=self.transport.submit_sm_resp,
            delivery_report=self.transport.delivery_report,
            deliver_sm=lambda: None)
        self.esme = EsmeTransceiver(self.transport.get_static_config(),
                                    self.transport.get_smpp_bind_params(),
                                    self.transport.redis, self.esme_callbacks)
        self.esme.sent_pdus = []
        self.esme.send_pdu = self.esme.sent_pdus.append
        self.esme.state = 'BOUND_TRX'

    def assert_sent_contents(self, expected):
        pdu_contents = [
            p.obj['body']['mandatory_parameters']['short_message']
            for p in self.esme.sent_pdus
        ]
        self.assertEqual(expected, pdu_contents)

    @inlineCallbacks
    def test_message_persistence(self):
        # A simple test of set -> get -> delete for redis message persistence
        message1 = self.tx_helper.make_outbound("hello world")
        original_json = message1.to_json()
        yield self.transport.r_set_message(message1)
        retrieved_json = yield self.transport.r_get_message_json(
            message1['message_id'])
        self.assertEqual(original_json, retrieved_json)
        retrieved_message = yield self.transport.r_get_message(
            message1['message_id'])
        self.assertEqual(retrieved_message, message1)
        self.assertTrue(
            (yield self.transport.r_delete_message(message1['message_id'])))
        self.assertEqual(
            (yield self.transport.r_get_message_json(message1['message_id'])),
            None)
        self.assertEqual(
            (yield self.transport.r_get_message(message1['message_id'])), None)

    @inlineCallbacks
    def test_message_persistence_expiry(self):
        message = self.tx_helper.make_outbound("hello world")
        yield self.transport.r_set_message(message)

        # check that the expiry is set
        message_key = self.transport.r_message_key(message['message_id'])
        config = self.transport.get_static_config()
        ttl = yield self.transport.redis.ttl(message_key)
        self.assertTrue(0 < ttl <= config.submit_sm_expiry)

    @inlineCallbacks
    def test_redis_third_party_id_persistence(self):
        # Testing: set -> get -> delete, for redis third party id mapping
        self.assertEqual(
            self.transport.get_static_config().third_party_id_expiry, 3600)
        our_id = "blergh34534545433454354"
        their_id = "omghesvomitingnumbers"
        yield self.transport.r_set_id_for_third_party_id(their_id, our_id)
        retrieved_our_id = (
            yield self.transport.r_get_id_for_third_party_id(their_id))
        self.assertEqual(our_id, retrieved_our_id)
        self.assertTrue((yield
                         self.transport.r_delete_for_third_party_id(their_id)))
        self.assertEqual(
            None, (yield self.transport.r_get_id_for_third_party_id(their_id)))

    @inlineCallbacks
    def test_out_of_order_responses(self):
        # Sequence numbers are hardcoded, assuming we start fresh from 0.
        yield self.tx_helper.make_dispatch_outbound("msg 1", message_id='444')
        response1 = SubmitSMResp(1, "3rd_party_id_1")

        yield self.tx_helper.make_dispatch_outbound("msg 2", message_id='445')
        response2 = SubmitSMResp(2, "3rd_party_id_2")

        self.assert_sent_contents(["msg 1", "msg 2"])
        # respond out of order - just to keep things interesting
        yield self.esme.handle_data(response2.get_bin())
        yield self.esme.handle_data(response1.get_bin())

        [ack1, ack2] = self.tx_helper.get_dispatched_events()
        self.assertEqual(ack1['user_message_id'], '445')
        self.assertEqual(ack1['sent_message_id'], '3rd_party_id_2')
        self.assertEqual(ack2['user_message_id'], '444')
        self.assertEqual(ack2['sent_message_id'], '3rd_party_id_1')

    @inlineCallbacks
    def test_failed_submit(self):
        message = yield self.tx_helper.make_dispatch_outbound("message",
                                                              message_id='446')
        response = SubmitSMResp(1,
                                "3rd_party_id_3",
                                command_status="ESME_RSUBMITFAIL")
        yield self.esme.handle_data(response.get_bin())

        self.assert_sent_contents(["message"])
        # There should be a nack
        [nack] = yield self.tx_helper.wait_for_dispatched_events(1)
        self.assertEqual(nack['user_message_id'], message['message_id'])
        self.assertEqual(nack['nack_reason'], 'ESME_RSUBMITFAIL')

        [failure] = yield self.tx_helper.get_dispatched_failures()
        self.assertEqual(failure['reason'], 'ESME_RSUBMITFAIL')

    @inlineCallbacks
    def test_failed_submit_with_no_reason(self):
        message = yield self.tx_helper.make_dispatch_outbound("message",
                                                              message_id='446')
        # Equivalent of SubmitSMResp(1, "3rd_party_id_3", command_status='XXX')
        # but with a bad command_status (pdu_builder can't produce binary with
        # command_statuses it doesn't understand). Use
        # smpp.pdu.unpack(response_bin) to get a PDU object:
        response_hex = (
            "0000001f80000004"
            "0000ffff"  # unknown command status
            "000000013372645f70617274795f69645f3300")
        yield self.esme.handle_data(binascii.a2b_hex(response_hex))

        self.assert_sent_contents(["message"])
        # There should be a nack
        [nack] = yield self.tx_helper.wait_for_dispatched_events(1)
        self.assertEqual(nack['user_message_id'], message['message_id'])
        self.assertEqual(nack['nack_reason'], 'Unspecified')

        [failure] = yield self.tx_helper.get_dispatched_failures()
        self.assertEqual(failure['reason'], 'Unspecified')

    @inlineCallbacks
    def test_delivery_report_for_unknown_message(self):
        dr = ("id:123 sub:... dlvrd:... submit date:200101010030"
              " done date:200101020030 stat:DELIVRD err:... text:Meep")
        deliver = DeliverSM(1, short_message=dr)
        with LogCatcher(message="Failed to retrieve message id") as lc:
            yield self.esme.handle_data(deliver.get_bin())
            [warning] = lc.logs
            self.assertEqual(warning['message'],
                             ("Failed to retrieve message id for delivery "
                              "report. Delivery report from %s "
                              "discarded." % self.tx_helper.transport_name, ))

    @inlineCallbacks
    def test_throttled_submit_ESME_RTHROTTLED(self):
        clock = Clock()
        self.transport.callLater = clock.callLater

        def assert_throttled_status(throttled, messages, acks):
            self.assertEqual(self.transport.throttled, throttled)
            self.assert_sent_contents(messages)
            self.assertEqual(acks,
                             [(m['user_message_id'], m['sent_message_id'])
                              for m in self.tx_helper.get_dispatched_events()])
            self.assertEqual([], self.tx_helper.get_dispatched_failures())

        assert_throttled_status(False, [], [])

        yield self.tx_helper.make_dispatch_outbound("Heimlich",
                                                    message_id="447")
        response = SubmitSMResp(1,
                                "3rd_party_id_4",
                                command_status="ESME_RTHROTTLED")
        yield self.esme.handle_data(response.get_bin())

        assert_throttled_status(True, ["Heimlich"], [])
        # Still waiting to resend
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich"], [])
        # Don't wait for this, because it won't be processed until later.
        self.tx_helper.make_dispatch_outbound("Other", message_id="448")
        assert_throttled_status(True, ["Heimlich"], [])
        # Resent
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich", "Heimlich"], [])
        # And acknowledged by the other side
        yield self.esme.handle_data(SubmitSMResp(2, "3rd_party_5").get_bin())
        yield self.tx_helper.kick_delivery()
        yield self.esme.handle_data(SubmitSMResp(3, "3rd_party_6").get_bin())
        assert_throttled_status(False, ["Heimlich", "Heimlich", "Other"],
                                [('447', '3rd_party_5'),
                                 ('448', '3rd_party_6')])

    @inlineCallbacks
    def test_throttled_submit_ESME_RMSGQFUL(self):
        clock = Clock()
        self.transport.callLater = clock.callLater

        def assert_throttled_status(throttled, messages, acks):
            self.assertEqual(self.transport.throttled, throttled)
            self.assert_sent_contents(messages)
            self.assertEqual(acks,
                             [(m['user_message_id'], m['sent_message_id'])
                              for m in self.tx_helper.get_dispatched_events()])
            self.assertEqual([], self.tx_helper.get_dispatched_failures())

        assert_throttled_status(False, [], [])

        yield self.tx_helper.make_dispatch_outbound("Heimlich",
                                                    message_id="447")
        response = SubmitSMResp(1,
                                "3rd_party_id_4",
                                command_status="ESME_RMSGQFUL")
        yield self.esme.handle_data(response.get_bin())

        assert_throttled_status(True, ["Heimlich"], [])
        # Still waiting to resend
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich"], [])
        # Don't wait for this, because it won't be processed until later.
        self.tx_helper.make_dispatch_outbound("Other", message_id="448")
        assert_throttled_status(True, ["Heimlich"], [])
        # Resent
        clock.advance(0.05)
        yield self.transport.redis.exists('wait for redis')
        assert_throttled_status(True, ["Heimlich", "Heimlich"], [])
        # And acknowledged by the other side
        yield self.esme.handle_data(SubmitSMResp(2, "3rd_party_5").get_bin())
        yield self.tx_helper.kick_delivery()
        yield self.esme.handle_data(SubmitSMResp(3, "3rd_party_6").get_bin())
        assert_throttled_status(False, ["Heimlich", "Heimlich", "Other"],
                                [('447', '3rd_party_5'),
                                 ('448', '3rd_party_6')])

    @inlineCallbacks
    def test_reconnect(self):
        connector = self.transport.connectors[self.transport.transport_name]
        self.assertFalse(connector._consumers['outbound'].paused)
        yield self.transport.esme_disconnected()
        self.assertTrue(connector._consumers['outbound'].paused)
        yield self.transport.esme_disconnected()
        self.assertTrue(connector._consumers['outbound'].paused)

        yield self.transport.esme_connected(self.esme)
        self.assertFalse(connector._consumers['outbound'].paused)
        yield self.transport.esme_connected(self.esme)
        self.assertFalse(connector._consumers['outbound'].paused)
Ejemplo n.º 6
0
 def __init__(self, *args, **kwargs):
     EsmeTransceiver.__init__(self, *args, **kwargs)
     self.setup_fake()