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 _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'
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)
def __init__(self, *args, **kwargs): EsmeTransceiver.__init__(self, *args, **kwargs) self.setup_fake()
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)