def test_bind_wrong_pdu(self): """ FakeSMSC will raise an exception if asked to bind with a non-bind PDU. """ fake_smsc = FakeSMSC() client = self.successResultOf(self.connect(fake_smsc)) bind_d = fake_smsc.bind() yield client.write(EnquireLink(0).get_bin()) self.failureResultOf(bind_d, ValueError)
def test_bind_mode_RX(self): """ FakeSMSC can accept receiver bind requests. """ fake_smsc = FakeSMSC() client = self.successResultOf(self.connect(fake_smsc)) self.assertEqual(client.received, b"") bind_d = fake_smsc.bind() yield client.write(BindReceiver(0).get_bin()) yield client.write(EnquireLink(1).get_bin()) self.assertEqual(client.received, b"".join([ BindReceiverResp(0).get_bin(), EnquireLinkResp(1).get_bin()])) yield wait0() self.successResultOf(bind_d)
def test_bind_mode_RX(self): """ FakeSMSC can accept receiver bind requests. """ fake_smsc = FakeSMSC() client = self.successResultOf(self.connect(fake_smsc)) self.assertEqual(client.received, b"") bind_d = fake_smsc.bind() yield client.write(BindReceiver(0).get_bin()) yield client.write(EnquireLink(1).get_bin()) self.assertEqual( client.received, b"".join( [BindReceiverResp(0).get_bin(), EnquireLinkResp(1).get_bin()])) yield wait0() self.successResultOf(bind_d)
def test_bind_explicit(self): """ FakeSMSC can bind using a PDU explicitly passed in. """ fake_smsc = FakeSMSC() client = self.successResultOf(self.connect(fake_smsc)) self.assertEqual(client.received, b"") bind_d = fake_smsc.bind(BindTransceiver(0).obj) yield wait0() # Bind response received. self.assertNoResult(bind_d) self.assertEqual(client.received, BindTransceiverResp(0).get_bin()) client.received = b"" yield client.write(EnquireLink(1).get_bin()) # enquire_link response received. self.assertNoResult(bind_d) self.assertEqual(client.received, EnquireLinkResp(1).get_bin()) yield wait0() # Bind complete. self.successResultOf(bind_d)
def test_bind(self): """ FakeSMSC can accept a bind request and respond to the first enquire_link. """ fake_smsc = FakeSMSC() client = self.successResultOf(self.connect(fake_smsc)) self.assertEqual(client.received, b"") bind_d = fake_smsc.bind() yield client.write(BindTransceiver(0).get_bin()) # Bind response received. self.assertNoResult(bind_d) self.assertEqual(client.received, BindTransceiverResp(0).get_bin()) client.received = b"" yield client.write(EnquireLink(1).get_bin()) # enquire_link response received. self.assertNoResult(bind_d) self.assertEqual(client.received, EnquireLinkResp(1).get_bin()) yield wait0() # Bind complete. self.successResultOf(bind_d)
class SixDeeProcessorTestCase(VumiTestCase): transport_class = SmppTransceiverTransport def setUp(self): self.clock = Clock() self.fake_smsc = FakeSMSC() self.tx_helper = self.add_helper( TransportHelper(SmppTransceiverTransport)) self.default_config = { 'transport_name': self.tx_helper.transport_name, 'twisted_endpoint': self.fake_smsc.endpoint, 'deliver_short_message_processor': ( 'vumi.transports.smpp.processors.sixdee.' 'DeliverShortMessageProcessor'), 'submit_short_message_processor': ( 'vumi.transports.smpp.processors.sixdee.' 'SubmitShortMessageProcessor'), 'system_id': 'foo', 'password': '******', 'deliver_short_message_processor_config': { 'data_coding_overrides': { 0: 'utf-8', } }, 'submit_short_message_processor_config': { 'submit_sm_encoding': 'utf-16be', 'submit_sm_data_coding': 8, 'send_multipart_udh': True, } } @inlineCallbacks def get_transport(self, deliver_config={}, submit_config={}, bind=True): cfg = self.default_config.copy() cfg['deliver_short_message_processor_config'].update(deliver_config) cfg['submit_short_message_processor_config'].update(submit_config) transport = yield self.tx_helper.get_transport(cfg, start=False) transport.clock = self.clock yield transport.startWorker() self.clock.advance(0) if bind: yield self.fake_smsc.bind() returnValue(transport) def assert_udh_parts(self, pdus, texts, encoding): def pdu_header(pdu): return short_message(pdu)[:6] def pdu_text(pdu): return short_message(pdu)[6:].decode(encoding) def udh_header(i): return '\x05\x00\x03\x03\x07' + chr(i) self.assertEqual( [(pdu_header(pdu), pdu_text(pdu)) for pdu in pdus], [(udh_header(i + 1), text) for i, text in enumerate(texts)]) @inlineCallbacks def test_submit_sm_multipart_udh_ucs2(self): message = ( "A cup is a small, open container used for carrying and " "drinking drinks. It may be made of wood, plastic, glass, " "clay, metal, stone, china or other materials, and may have " "a stem, handles or other adornments. Cups are used for " "drinking across a wide range of cultures and social classes, " "and different styles of cups may be used for different liquids " "or in different situations. Cups have been used for thousands " "of years for the ...Reply 1 for more") yield self.get_transport() yield self.tx_helper.make_dispatch_outbound(message, to_addr='msisdn') pdus = yield self.fake_smsc.await_pdus(7) self.assert_udh_parts(pdus, [ ("A cup is a small, open container used" " for carrying and drinking d"), ("rinks. It may be made of wood, plastic," " glass, clay, metal, stone"), (", china or other materials, and may have" " a stem, handles or other"), (" adornments. Cups are used for drinking" " across a wide range of cu"), ("ltures and social classes, and different" " styles of cups may be us"), ("ed for different liquids or in different" " situations. Cups have be"), ("en used for thousands of years for the ...Reply 1 for more"), ], encoding='utf-16be') # utf-16be is close enough to UCS2 for pdu in pdus: self.assertTrue(len(short_message(pdu)) < 140) @inlineCallbacks def test_submit_and_deliver_ussd_new(self): session = SessionInfo() yield self.get_transport() # Server delivers a USSD message to the Client pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess['content'], None) self.assertEqual(mess['to_addr'], '*123#') self.assertEqual(mess['transport_type'], "ussd") self.assertEqual(mess['session_event'], TransportUserMessage.SESSION_NEW) self.assertEqual( mess['transport_metadata'], { 'session_info': { 'session_identifier': session.sixdee_id, 'ussd_service_op': '01', } }) @inlineCallbacks def test_submit_and_deliver_ussd_new_custom_ussd_code_field(self): session = SessionInfo() yield self.get_transport(deliver_config={ 'ussd_code_pdu_field': 'destination_addr', }) # Server delivers a USSD message to the Client pdu = DeliverSM(1, short_message="*IGNORE#", destination_addr="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess['content'], None) self.assertEqual(mess['to_addr'], '*123#') self.assertEqual(mess['transport_type'], "ussd") self.assertEqual(mess['session_event'], TransportUserMessage.SESSION_NEW) self.assertEqual( mess['transport_metadata'], { 'session_info': { 'session_identifier': session.sixdee_id, 'ussd_service_op': '01', } }) @inlineCallbacks def test_deliver_sm_op_codes_new(self): session = SessionInfo() yield self.get_transport() pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [start] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(start['session_event'], TransportUserMessage.SESSION_NEW) @inlineCallbacks def test_deliver_sm_op_codes_resume(self): session = SessionInfo() transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session( session.vumi_id, ussd_code='*123#') pdu = DeliverSM(1, short_message="", source_addr=session.addr) pdu.add_optional_parameter('ussd_service_op', '12') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [resume] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(resume['session_event'], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_deliver_sm_op_codes_end(self): session = SessionInfo() transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session( session.vumi_id, ussd_code='*123#') pdu = DeliverSM(1, short_message="", source_addr=session.addr) pdu.add_optional_parameter('ussd_service_op', '81') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [end] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(end['session_event'], TransportUserMessage.SESSION_CLOSE) @inlineCallbacks def test_deliver_sm_unknown_op_code(self): session = SessionInfo() yield self.get_transport() pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '99') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [start, unknown] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(unknown['session_event'], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_submit_sm_op_codes_resume(self): session = SessionInfo() yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_RESUME, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) resume = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(resume, 'ussd_service_op'), '02') self.assertEqual(pdu_tlv(resume, 'its_session_info'), session.its_info) @inlineCallbacks def test_submit_sm_op_codes_close(self): session = SessionInfo(continue_session=False) yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_CLOSE, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) close = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(close, 'ussd_service_op'), '17') self.assertEqual(pdu_tlv(close, 'its_session_info'), session.its_info) @inlineCallbacks def test_submit_and_deliver_ussd_continue(self): session = SessionInfo() transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session( session.vumi_id, ussd_code='*123#') yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(pdu_tlv(submit_sm_pdu, 'ussd_service_op'), '02') self.assertEqual(pdu_tlv(submit_sm_pdu, 'its_session_info'), session.its_info) # Server delivers a USSD message to the Client pdu = DeliverSM(seq_no(submit_sm_pdu) + 1, short_message="reply!", source_addr=session.addr) # 0x12 is 'continue' pdu.add_optional_parameter('ussd_service_op', '12') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess['content'], "reply!") self.assertEqual(mess['transport_type'], "ussd") self.assertEqual(mess['to_addr'], '*123#') self.assertEqual(mess['session_event'], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_submit_and_deliver_ussd_close(self): session = SessionInfo(continue_session=False) yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_CLOSE, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(pdu_tlv(submit_sm_pdu, 'ussd_service_op'), '17') self.assertEqual(pdu_tlv(submit_sm_pdu, 'its_session_info'), session.its_info) @inlineCallbacks def test_submit_sm_null_message(self): """ We can successfully send a message with null content. """ session = SessionInfo() yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( None, transport_type="ussd", session_event=TransportUserMessage.SESSION_RESUME, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) resume = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(resume, 'ussd_service_op'), '02') self.assertEqual(pdu_tlv(resume, 'its_session_info'), session.its_info)
class SixDeeProcessorTestCase(VumiTestCase): transport_class = SmppTransceiverTransport def setUp(self): self.clock = Clock() self.fake_smsc = FakeSMSC() self.tx_helper = self.add_helper( TransportHelper(SmppTransceiverTransport)) self.default_config = { 'transport_name': self.tx_helper.transport_name, 'twisted_endpoint': self.fake_smsc.endpoint, 'deliver_short_message_processor': ('vumi.transports.smpp.processors.sixdee.' 'DeliverShortMessageProcessor'), 'submit_short_message_processor': ('vumi.transports.smpp.processors.sixdee.' 'SubmitShortMessageProcessor'), 'system_id': 'foo', 'password': '******', 'deliver_short_message_processor_config': { 'data_coding_overrides': { 0: 'utf-8', } }, 'submit_short_message_processor_config': { 'submit_sm_encoding': 'utf-16be', 'submit_sm_data_coding': 8, 'send_multipart_udh': True, } } @inlineCallbacks def get_transport(self, config={}, bind=True): cfg = self.default_config.copy() transport = yield self.tx_helper.get_transport(cfg, start=False) transport.clock = self.clock yield transport.startWorker() self.clock.advance(0) if bind: yield self.fake_smsc.bind() returnValue(transport) def assert_udh_parts(self, pdus, texts, encoding): pdu_header = lambda pdu: short_message(pdu)[:6] pdu_text = lambda pdu: short_message(pdu)[6:].decode(encoding) udh_header = lambda i: '\x05\x00\x03\x03\x07' + chr(i) self.assertEqual([(pdu_header(pdu), pdu_text(pdu)) for pdu in pdus], [(udh_header(i + 1), text) for i, text in enumerate(texts)]) @inlineCallbacks def test_submit_sm_multipart_udh_ucs2(self): message = ( "A cup is a small, open container used for carrying and " "drinking drinks. It may be made of wood, plastic, glass, " "clay, metal, stone, china or other materials, and may have " "a stem, handles or other adornments. Cups are used for " "drinking across a wide range of cultures and social classes, " "and different styles of cups may be used for different liquids " "or in different situations. Cups have been used for thousands " "of years for the ...Reply 1 for more") yield self.get_transport() yield self.tx_helper.make_dispatch_outbound(message, to_addr='msisdn') pdus = yield self.fake_smsc.await_pdus(7) self.assert_udh_parts( pdus, [ ("A cup is a small, open container used" " for carrying and drinking d"), ("rinks. It may be made of wood, plastic," " glass, clay, metal, stone"), (", china or other materials, and may have" " a stem, handles or other"), (" adornments. Cups are used for drinking" " across a wide range of cu"), ("ltures and social classes, and different" " styles of cups may be us"), ("ed for different liquids or in different" " situations. Cups have be"), ("en used for thousands of years for the ...Reply 1 for more"), ], encoding='utf-16be') # utf-16be is close enough to UCS2 for pdu in pdus: self.assertTrue(len(short_message(pdu)) < 140) @inlineCallbacks def test_submit_and_deliver_ussd_new(self): session = SessionInfo() yield self.get_transport() # Server delivers a USSD message to the Client pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess['content'], None) self.assertEqual(mess['to_addr'], '*123#') self.assertEqual(mess['transport_type'], "ussd") self.assertEqual(mess['session_event'], TransportUserMessage.SESSION_NEW) self.assertEqual( mess['transport_metadata'], { 'session_info': { 'session_identifier': session.sixdee_id, 'ussd_service_op': '01', } }) @inlineCallbacks def test_deliver_sm_op_codes_new(self): session = SessionInfo() yield self.get_transport() pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [start] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(start['session_event'], TransportUserMessage.SESSION_NEW) @inlineCallbacks def test_deliver_sm_op_codes_resume(self): session = SessionInfo() transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session(session.vumi_id, ussd_code='*123#') pdu = DeliverSM(1, short_message="", source_addr=session.addr) pdu.add_optional_parameter('ussd_service_op', '12') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [resume] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(resume['session_event'], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_deliver_sm_op_codes_end(self): session = SessionInfo() transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session(session.vumi_id, ussd_code='*123#') pdu = DeliverSM(1, short_message="", source_addr=session.addr) pdu.add_optional_parameter('ussd_service_op', '81') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [end] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(end['session_event'], TransportUserMessage.SESSION_CLOSE) @inlineCallbacks def test_deliver_sm_unknown_op_code(self): session = SessionInfo() yield self.get_transport() pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '01') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter('ussd_service_op', '99') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [start, unknown] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(unknown['session_event'], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_submit_sm_op_codes_resume(self): session = SessionInfo() yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_RESUME, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) resume = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(resume, 'ussd_service_op'), '02') self.assertEqual(pdu_tlv(resume, 'its_session_info'), session.its_info) @inlineCallbacks def test_submit_sm_op_codes_close(self): session = SessionInfo(continue_session=False) yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_CLOSE, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) close = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(close, 'ussd_service_op'), '17') self.assertEqual(pdu_tlv(close, 'its_session_info'), session.its_info) @inlineCallbacks def test_submit_and_deliver_ussd_continue(self): session = SessionInfo() transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session(session.vumi_id, ussd_code='*123#') yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(pdu_tlv(submit_sm_pdu, 'ussd_service_op'), '02') self.assertEqual(pdu_tlv(submit_sm_pdu, 'its_session_info'), session.its_info) # Server delivers a USSD message to the Client pdu = DeliverSM(seq_no(submit_sm_pdu) + 1, short_message="reply!", source_addr=session.addr) # 0x12 is 'continue' pdu.add_optional_parameter('ussd_service_op', '12') pdu.add_optional_parameter('its_session_info', session.its_info) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess['content'], "reply!") self.assertEqual(mess['transport_type'], "ussd") self.assertEqual(mess['to_addr'], '*123#') self.assertEqual(mess['session_event'], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_submit_and_deliver_ussd_close(self): session = SessionInfo(continue_session=False) yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_CLOSE, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(pdu_tlv(submit_sm_pdu, 'ussd_service_op'), '17') self.assertEqual(pdu_tlv(submit_sm_pdu, 'its_session_info'), session.its_info) @inlineCallbacks def test_submit_sm_null_message(self): """ We can successfully send a message with null content. """ session = SessionInfo() yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( None, transport_type="ussd", session_event=TransportUserMessage.SESSION_RESUME, transport_metadata={ 'session_info': { 'session_identifier': session.sixdee_id, } }, to_addr=session.addr) resume = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(resume, 'ussd_service_op'), '02') self.assertEqual(pdu_tlv(resume, 'its_session_info'), session.its_info)
class DefaultProcessorTestCase(VumiTestCase): def setUp(self): self.fake_smsc = FakeSMSC() self.tx_helper = self.add_helper( TransportHelper(SmppTransceiverTransport)) self.clock = Clock() @inlineCallbacks def get_transport(self, config): transport = yield self.tx_helper.get_transport(config, start=False) transport.clock = self.clock yield transport.startWorker() self.clock.advance(0) yield self.fake_smsc.bind() returnValue(transport) @inlineCallbacks def test_data_coding_override_keys_ints(self): """ If the keys of the data coding overrides config dictionary are not integers, they should be cast to integers. """ config = { 'system_id': 'foo', 'password': '******', 'twisted_endpoint': self.fake_smsc.endpoint, 'deliver_short_message_processor_config': { 'data_coding_overrides': { '0': 'utf-8' }, }, } transport = yield self.tx_helper.get_transport(config) self.assertEqual(transport.deliver_sm_processor.data_coding_map.get(0), 'utf-8') @inlineCallbacks def test_data_coding_override_keys_invalid(self): """ If the keys of the data coding overrides config dictionary can not be cast to integers, a config error with an appropriate message should be raised. """ config = { 'system_id': 'foo', 'password': '******', 'twisted_endpoint': self.fake_smsc.endpoint, 'deliver_short_message_processor_config': { 'data_coding_overrides': { 'not-an-int': 'utf-8' }, }, } try: yield self.tx_helper.get_transport(config) except ConfigError as e: self.assertEqual( str(e), "data_coding_overrides keys must be castable to ints. " "invalid literal for int() with base 10: 'not-an-int'") else: raise FailTest("Expected ConfigError to be raised") @inlineCallbacks def test_multipart_sar_reference_rollover(self): """ If the multipart_sar_reference_rollover config value is set, then for multipart messages, the reference should rollover at that value. """ config = { 'system_id': 'foo', 'password': '******', 'twisted_endpoint': self.fake_smsc.endpoint, 'submit_short_message_processor_config': { 'send_multipart_sar': True, 'multipart_sar_reference_rollover': 0xFF, }, } transport = yield self.get_transport(config) transport.service.sequence_generator.redis.set( 'smpp_last_sequence_number', 0xFF) yield transport.submit_sm_processor.send_short_message( transport.service, 'test-id', '+1234', 'test message ' * 20, optional_parameters={}) pdus = yield self.fake_smsc.await_pdus(2) msg_refs = [unpacked_pdu_opts(p)['sar_msg_ref_num'] for p in pdus] self.assertEqual(msg_refs, [1, 1])
class TestSmppService(VumiTestCase): @inlineCallbacks def setUp(self): self.clock = Clock() self.persistence_helper = self.add_helper(PersistenceHelper()) self.redis = yield self.persistence_helper.get_redis_manager() self.fake_smsc = FakeSMSC(auto_accept=False) self.default_config = { 'transport_name': 'sphex_transport', 'twisted_endpoint': self.fake_smsc.endpoint, 'system_id': 'system_id', 'password': '******', } def get_service(self, config={}, bind_type='TRX', start=True): """ Create and optionally start a new service object. """ cfg = self.default_config.copy() cfg.update(config) dummy_transport = DummySmppTransport(self.clock, self.redis, cfg) service = SmppService(self.fake_smsc.endpoint, bind_type, dummy_transport) service.clock = self.clock d = succeed(service) if start: d.addCallback(self.start_service) return d def start_service(self, service, accept_connection=True): """ Start the given service. """ service.startService() self.clock.advance(0) d = self.fake_smsc.await_connecting() if accept_connection: d.addCallback(lambda _: self.fake_smsc.accept_connection()) return d.addCallback(lambda _: service) def lookup_message_ids(self, service, seq_nums): """ Find vumi message ids associated with SMPP sequence numbers. """ lookup_func = service.message_stash.get_sequence_number_message_id return gatherResults([lookup_func(seq_num) for seq_num in seq_nums]) def set_sequence_number(self, service, seq_nr): return service.sequence_generator.redis.set( 'smpp_last_sequence_number', seq_nr) @inlineCallbacks def test_start_sequence(self): """ The service goes through several states while starting. """ # New service, never started. service = yield self.get_service(start=False) self.assertEqual(service.running, False) self.assertEqual(service.get_bind_state(), EsmeProtocol.CLOSED_STATE) # Start, but don't connect. yield self.start_service(service, accept_connection=False) self.assertEqual(service.running, True) self.assertEqual(service.get_bind_state(), EsmeProtocol.CLOSED_STATE) # Connect, but don't bind. yield self.fake_smsc.accept_connection() self.assertEqual(service.running, True) self.assertEqual(service.get_bind_state(), EsmeProtocol.OPEN_STATE) bind_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(bind_pdu), 'bind_transceiver') # Bind. yield self.fake_smsc.bind(bind_pdu) self.assertEqual(service.running, True) self.assertEqual(service.get_bind_state(), EsmeProtocol.BOUND_STATE_TRX) @inlineCallbacks def test_connect_retries(self): """ If we fail to connect, we retry. """ service = yield self.get_service(start=False) self.assertEqual(self.fake_smsc.has_pending_connection(), False) # Start, but don't connect. yield self.start_service(service, accept_connection=False) self.assertEqual(self.fake_smsc.has_pending_connection(), True) self.assertEqual(service._protocol, None) self.assertEqual(service.retries, 1) # Reject the connection. yield self.fake_smsc.reject_connection() self.assertEqual(service._protocol, None) self.assertEqual(service.retries, 2) # Advance to the next connection attempt. self.clock.advance(service.delay) self.assertEqual(self.fake_smsc.has_pending_connection(), True) self.assertEqual(service._protocol, None) self.assertEqual(service.retries, 2) # Accept the connection. yield self.fake_smsc.accept_connection() self.assertEqual(service.running, True) self.assertNotEqual(service._protocol, None) @inlineCallbacks def test_submit_sm(self): """ When bound, we can send a message. """ service = yield self.get_service() yield self.fake_smsc.bind() seq_nums = yield service.submit_sm('abc123', 'dest_addr', short_message='foo') submit_sm = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm), 'submit_sm') stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'], stored_ids) @inlineCallbacks def test_submit_sm_unbound(self): """ When unbound, we can't send a message. """ service = yield self.get_service() self.assertRaises(EsmeProtocolError, service.submit_sm, 'abc123', 'dest_addr', short_message='foo') @inlineCallbacks def test_submit_sm_not_connected(self): """ When not connected, we can't send a message. """ service = yield self.get_service(start=False) yield self.start_service(service, accept_connection=False) self.assertRaises(EsmeProtocolError, service.submit_sm, 'abc123', 'dest_addr', short_message='foo') @skiptest("FIXME: We don't actually unbind and disconnect yet.") @inlineCallbacks def test_handle_unbind(self): """ If the SMSC sends an unbind command, we respond and disconnect. """ service = yield self.get_service() yield self.fake_smsc.bind() self.assertEqual(service.is_bound(), True) self.fake_smsc.send_pdu(Unbind(7)) unbind_resp_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(unbind_resp_pdu), 'unbind_resp') self.assertEqual(service.is_bound(), False) @inlineCallbacks def test_csm_split_message(self): """ A multipart message is split into chunks such that the smallest number of message parts are required. """ service = yield self.get_service() split = lambda msg: service.csm_split_message(msg.encode('utf-8')) # these are fine because they're in the 7-bit character set self.assertEqual(1, len(split(u'&' * 140))) self.assertEqual(1, len(split(u'&' * 160))) # ± is not in the 7-bit character set so it should utf-8 encode it # which bumps it over the 140 bytes self.assertEqual(2, len(split(u'±' + u'1' * 139))) @inlineCallbacks def test_submit_sm_long(self): """ A long message can be sent in a single PDU using the optional `message_payload` PDU field. """ service = yield self.get_service() yield self.fake_smsc.bind() long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_sm_long('abc123', 'dest_addr', long_message) submit_sm = yield self.fake_smsc.await_pdu() pdu_opts = unpacked_pdu_opts(submit_sm) self.assertEqual('submit_sm', submit_sm['header']['command_id']) self.assertEqual( None, submit_sm['body']['mandatory_parameters']['short_message']) self.assertEqual(''.join('%02x' % ord(c) for c in long_message), pdu_opts['message_payload']) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'], stored_ids) @inlineCallbacks def test_submit_csm_sar(self): """ A long message can be sent in multiple PDUs with SAR fields set to instruct the SMSC to build user data headers. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_csm_sar('abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) # seq no 1 == bind_transceiver, 2 == enquire_link, 3 == sar_msg_ref_num self.assertEqual([4, 5, 6, 7], seq_nums) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): pdu_opts = unpacked_pdu_opts(sm) mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg_parts.append(mandatory_parameters['short_message']) self.assertTrue(len(mandatory_parameters['short_message']) <= 130) msg_refs.append(pdu_opts['sar_msg_ref_num']) self.assertEqual(i + 1, pdu_opts['sar_segment_seqnum']) self.assertEqual(4, pdu_opts['sar_total_segments']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual([3, 3, 3, 3], msg_refs) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'] * len(seq_nums), stored_ids) @inlineCallbacks def test_submit_csm_sar_ref_num_limit(self): """ The SAR reference number is set correctly when the generated reference number is larger than 0xFFFF. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() # forward until we go past 0xFFFF yield self.set_sequence_number(service, 0x10000) long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_csm_sar('abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): pdu_opts = unpacked_pdu_opts(sm) mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg_parts.append(mandatory_parameters['short_message']) self.assertTrue(len(mandatory_parameters['short_message']) <= 130) msg_refs.append(pdu_opts['sar_msg_ref_num']) self.assertEqual(i + 1, pdu_opts['sar_segment_seqnum']) self.assertEqual(4, pdu_opts['sar_total_segments']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual([1, 1, 1, 1], msg_refs) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'] * len(seq_nums), stored_ids) @inlineCallbacks def test_submit_csm_sar_ref_num_custom_limit(self): """ The SAR reference number is set correctly when the generated reference number is larger than the configured limit. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() # forward until we go past 0xFF yield self.set_sequence_number(service, 0x100) long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_csm_sar('abc123', 'dest_addr', short_message=long_message, reference_rollover=0x100) pdus = yield self.fake_smsc.await_pdus(4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): pdu_opts = unpacked_pdu_opts(sm) mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg_parts.append(mandatory_parameters['short_message']) self.assertTrue(len(mandatory_parameters['short_message']) <= 130) msg_refs.append(pdu_opts['sar_msg_ref_num']) self.assertEqual(i + 1, pdu_opts['sar_segment_seqnum']) self.assertEqual(4, pdu_opts['sar_total_segments']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual([1, 1, 1, 1], msg_refs) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'] * len(seq_nums), stored_ids) @inlineCallbacks def test_submit_csm_sar_single_part(self): """ If the content fits in a single message, all the multipart madness is avoided. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() content = 'a' * 160 seq_numbers = yield service.submit_csm_sar('abc123', 'dest_addr', short_message=content) self.assertEqual(len(seq_numbers), 1) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(short_message(submit_sm_pdu), content) self.assertEqual(unpacked_pdu_opts(submit_sm_pdu), {}) @inlineCallbacks def test_submit_csm_udh(self): """ A long message can be sent in multiple PDUs with carefully handcrafted user data headers. """ service = yield self.get_service({'send_multipart_udh': True}) yield self.fake_smsc.bind() long_message = 'This is a long message.' * 20 seq_numbers = yield service.submit_csm_udh('abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) self.assertEqual(len(seq_numbers), 4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg = mandatory_parameters['short_message'] udh_hlen, udh_tag, udh_len, udh_ref, udh_tot, udh_seq = [ ord(octet) for octet in msg[:6] ] self.assertEqual(5, udh_hlen) self.assertEqual(0, udh_tag) self.assertEqual(3, udh_len) msg_refs.append(udh_ref) self.assertEqual(4, udh_tot) self.assertEqual(i + 1, udh_seq) self.assertTrue(len(msg) <= 136) msg_parts.append(msg[6:]) self.assertEqual(0x40, mandatory_parameters['esm_class']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual(1, len(set(msg_refs))) stored_ids = yield self.lookup_message_ids(service, seq_numbers) self.assertEqual(['abc123'] * len(seq_numbers), stored_ids) @inlineCallbacks def test_submit_csm_udh_ref_num_limit(self): """ User data headers are crafted correctly when the generated reference number is larger than 0xFF. """ service = yield self.get_service({'send_multipart_udh': True}) yield self.fake_smsc.bind() # forward until we go past 0xFF yield self.set_sequence_number(service, 0x100) long_message = 'This is a long message.' * 20 seq_numbers = yield service.submit_csm_udh('abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) self.assertEqual(len(seq_numbers), 4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg = mandatory_parameters['short_message'] udh_hlen, udh_tag, udh_len, udh_ref, udh_tot, udh_seq = [ ord(octet) for octet in msg[:6] ] self.assertEqual(5, udh_hlen) self.assertEqual(0, udh_tag) self.assertEqual(3, udh_len) msg_refs.append(udh_ref) self.assertEqual(4, udh_tot) self.assertEqual(i + 1, udh_seq) self.assertTrue(len(msg) <= 136) msg_parts.append(msg[6:]) self.assertEqual(0x40, mandatory_parameters['esm_class']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual(1, len(set(msg_refs))) stored_ids = yield self.lookup_message_ids(service, seq_numbers) self.assertEqual(['abc123'] * len(seq_numbers), stored_ids) @inlineCallbacks def test_submit_csm_udh_single_part(self): """ If the content fits in a single message, all the multipart madness is avoided. """ service = yield self.get_service({'send_multipart_udh': True}) yield self.fake_smsc.bind() content = 'a' * 160 seq_numbers = yield service.submit_csm_udh('abc123', 'dest_addr', short_message=content) self.assertEqual(len(seq_numbers), 1) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(short_message(submit_sm_pdu), content) self.assertEqual( submit_sm_pdu['body']['mandatory_parameters']['esm_class'], 0) @inlineCallbacks def test_pdu_cache_persistence(self): """ A cached PDU has an appropriate TTL and can be deleted. """ service = yield self.get_service() message_stash = service.message_stash config = service.get_config() pdu = SubmitSM(1337, short_message="foo") yield message_stash.cache_pdu("vumi0", pdu) ttl = yield message_stash.redis.ttl(pdu_key(1337)) self.assertTrue(0 < ttl <= config.submit_sm_expiry) pdu_data = yield message_stash.get_cached_pdu(1337) self.assertEqual(pdu_data.vumi_message_id, "vumi0") self.assertEqual(pdu_data.pdu.get_hex(), pdu.get_hex()) yield message_stash.delete_cached_pdu(1337) deleted_pdu_data = yield message_stash.get_cached_pdu(1337) self.assertEqual(deleted_pdu_data, None)
class DefaultProcessorTestCase(VumiTestCase): def setUp(self): self.fake_smsc = FakeSMSC() self.tx_helper = self.add_helper( TransportHelper(SmppTransceiverTransport)) self.clock = Clock() @inlineCallbacks def get_transport(self, config): transport = yield self.tx_helper.get_transport(config, start=False) transport.clock = self.clock yield transport.startWorker() self.clock.advance(0) yield self.fake_smsc.bind() returnValue(transport) @inlineCallbacks def test_data_coding_override_keys_ints(self): """ If the keys of the data coding overrides config dictionary are not integers, they should be cast to integers. """ config = { 'system_id': 'foo', 'password': '******', 'twisted_endpoint': self.fake_smsc.endpoint, 'deliver_short_message_processor_config': { 'data_coding_overrides': { '0': 'utf-8' }, }, } transport = yield self.tx_helper.get_transport(config) self.assertEqual( transport.deliver_sm_processor.data_coding_map.get(0), 'utf-8') @inlineCallbacks def test_data_coding_override_keys_invalid(self): """ If the keys of the data coding overrides config dictionary can not be cast to integers, a config error with an appropriate message should be raised. """ config = { 'system_id': 'foo', 'password': '******', 'twisted_endpoint': self.fake_smsc.endpoint, 'deliver_short_message_processor_config': { 'data_coding_overrides': { 'not-an-int': 'utf-8' }, }, } try: yield self.tx_helper.get_transport(config) except ConfigError as e: self.assertEqual( str(e), "data_coding_overrides keys must be castable to ints. " "invalid literal for int() with base 10: 'not-an-int'" ) else: raise FailTest("Expected ConfigError to be raised") @inlineCallbacks def test_multipart_sar_reference_rollover(self): """ If the multipart_sar_reference_rollover config value is set, then for multipart messages, the reference should rollover at that value. """ config = { 'system_id': 'foo', 'password': '******', 'twisted_endpoint': self.fake_smsc.endpoint, 'submit_short_message_processor_config': { 'send_multipart_sar': True, 'multipart_sar_reference_rollover': 0xFF, }, } transport = yield self.get_transport(config) transport.service.sequence_generator.redis.set( 'smpp_last_sequence_number', 0xFF) yield transport.submit_sm_processor.send_short_message( transport.service, 'test-id', '+1234', 'test message ' * 20, optional_parameters={}) pdus = yield self.fake_smsc.await_pdus(2) msg_refs = [unpacked_pdu_opts(p)['sar_msg_ref_num'] for p in pdus] self.assertEqual(msg_refs, [1, 1])
class TestSmppService(VumiTestCase): @inlineCallbacks def setUp(self): self.clock = Clock() self.persistence_helper = self.add_helper(PersistenceHelper()) self.redis = yield self.persistence_helper.get_redis_manager() self.fake_smsc = FakeSMSC(auto_accept=False) self.default_config = { 'transport_name': 'sphex_transport', 'twisted_endpoint': self.fake_smsc.endpoint, 'system_id': 'system_id', 'password': '******', } def get_service(self, config={}, bind_type='TRX', start=True): """ Create and optionally start a new service object. """ cfg = self.default_config.copy() cfg.update(config) dummy_transport = DummySmppTransport(self.clock, self.redis, cfg) service = SmppService( self.fake_smsc.endpoint, bind_type, dummy_transport) service.clock = self.clock d = succeed(service) if start: d.addCallback(self.start_service) return d def start_service(self, service, accept_connection=True): """ Start the given service. """ service.startService() self.clock.advance(0) d = self.fake_smsc.await_connecting() if accept_connection: d.addCallback(lambda _: self.fake_smsc.accept_connection()) return d.addCallback(lambda _: service) def lookup_message_ids(self, service, seq_nums): """ Find vumi message ids associated with SMPP sequence numbers. """ lookup_func = service.message_stash.get_sequence_number_message_id return gatherResults([lookup_func(seq_num) for seq_num in seq_nums]) def set_sequence_number(self, service, seq_nr): return service.sequence_generator.redis.set( 'smpp_last_sequence_number', seq_nr) @inlineCallbacks def test_start_sequence(self): """ The service goes through several states while starting. """ # New service, never started. service = yield self.get_service(start=False) self.assertEqual(service.running, False) self.assertEqual(service.get_bind_state(), EsmeProtocol.CLOSED_STATE) # Start, but don't connect. yield self.start_service(service, accept_connection=False) self.assertEqual(service.running, True) self.assertEqual(service.get_bind_state(), EsmeProtocol.CLOSED_STATE) # Connect, but don't bind. yield self.fake_smsc.accept_connection() self.assertEqual(service.running, True) self.assertEqual(service.get_bind_state(), EsmeProtocol.OPEN_STATE) bind_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(bind_pdu), 'bind_transceiver') # Bind. yield self.fake_smsc.bind(bind_pdu) self.assertEqual(service.running, True) self.assertEqual( service.get_bind_state(), EsmeProtocol.BOUND_STATE_TRX) @inlineCallbacks def test_connect_retries(self): """ If we fail to connect, we retry. """ service = yield self.get_service(start=False) self.assertEqual(self.fake_smsc.has_pending_connection(), False) # Start, but don't connect. yield self.start_service(service, accept_connection=False) self.assertEqual(self.fake_smsc.has_pending_connection(), True) self.assertEqual(service._protocol, None) self.assertEqual(service.retries, 1) # Reject the connection. yield self.fake_smsc.reject_connection() self.assertEqual(service._protocol, None) self.assertEqual(service.retries, 2) # Advance to the next connection attempt. self.clock.advance(service.delay) self.assertEqual(self.fake_smsc.has_pending_connection(), True) self.assertEqual(service._protocol, None) self.assertEqual(service.retries, 2) # Accept the connection. yield self.fake_smsc.accept_connection() self.assertEqual(service.running, True) self.assertNotEqual(service._protocol, None) @inlineCallbacks def test_submit_sm(self): """ When bound, we can send a message. """ service = yield self.get_service() yield self.fake_smsc.bind() seq_nums = yield service.submit_sm( 'abc123', 'dest_addr', short_message='foo') submit_sm = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm), 'submit_sm') stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'], stored_ids) @inlineCallbacks def test_submit_sm_unbound(self): """ When unbound, we can't send a message. """ service = yield self.get_service() self.assertRaises( EsmeProtocolError, service.submit_sm, 'abc123', 'dest_addr', short_message='foo') @inlineCallbacks def test_submit_sm_not_connected(self): """ When not connected, we can't send a message. """ service = yield self.get_service(start=False) yield self.start_service(service, accept_connection=False) self.assertRaises( EsmeProtocolError, service.submit_sm, 'abc123', 'dest_addr', short_message='foo') @skiptest("FIXME: We don't actually unbind and disconnect yet.") @inlineCallbacks def test_handle_unbind(self): """ If the SMSC sends an unbind command, we respond and disconnect. """ service = yield self.get_service() yield self.fake_smsc.bind() self.assertEqual(service.is_bound(), True) self.fake_smsc.send_pdu(Unbind(7)) unbind_resp_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(unbind_resp_pdu), 'unbind_resp') self.assertEqual(service.is_bound(), False) @inlineCallbacks def test_csm_split_message(self): """ A multipart message is split into chunks such that the smallest number of message parts are required. """ service = yield self.get_service() split = lambda msg: service.csm_split_message(msg.encode('utf-8')) # these are fine because they're in the 7-bit character set self.assertEqual(1, len(split(u'&' * 140))) self.assertEqual(1, len(split(u'&' * 160))) # ± is not in the 7-bit character set so it should utf-8 encode it # which bumps it over the 140 bytes self.assertEqual(2, len(split(u'±' + u'1' * 139))) @inlineCallbacks def test_submit_sm_long(self): """ A long message can be sent in a single PDU using the optional `message_payload` PDU field. """ service = yield self.get_service() yield self.fake_smsc.bind() long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_sm_long( 'abc123', 'dest_addr', long_message) submit_sm = yield self.fake_smsc.await_pdu() pdu_opts = unpacked_pdu_opts(submit_sm) self.assertEqual('submit_sm', submit_sm['header']['command_id']) self.assertEqual( None, submit_sm['body']['mandatory_parameters']['short_message']) self.assertEqual(''.join('%02x' % ord(c) for c in long_message), pdu_opts['message_payload']) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'], stored_ids) @inlineCallbacks def test_submit_csm_sar(self): """ A long message can be sent in multiple PDUs with SAR fields set to instruct the SMSC to build user data headers. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_csm_sar( 'abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) # seq no 1 == bind_transceiver, 2 == enquire_link, 3 == sar_msg_ref_num self.assertEqual([4, 5, 6, 7], seq_nums) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): pdu_opts = unpacked_pdu_opts(sm) mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg_parts.append(mandatory_parameters['short_message']) self.assertTrue(len(mandatory_parameters['short_message']) <= 130) msg_refs.append(pdu_opts['sar_msg_ref_num']) self.assertEqual(i + 1, pdu_opts['sar_segment_seqnum']) self.assertEqual(4, pdu_opts['sar_total_segments']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual([3, 3, 3, 3], msg_refs) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'] * len(seq_nums), stored_ids) @inlineCallbacks def test_submit_csm_sar_ref_num_limit(self): """ The SAR reference number is set correctly when the generated reference number is larger than 0xFFFF. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() # forward until we go past 0xFFFF yield self.set_sequence_number(service, 0x10000) long_message = 'This is a long message.' * 20 seq_nums = yield service.submit_csm_sar( 'abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): pdu_opts = unpacked_pdu_opts(sm) mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg_parts.append(mandatory_parameters['short_message']) self.assertTrue(len(mandatory_parameters['short_message']) <= 130) msg_refs.append(pdu_opts['sar_msg_ref_num']) self.assertEqual(i + 1, pdu_opts['sar_segment_seqnum']) self.assertEqual(4, pdu_opts['sar_total_segments']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual([2, 2, 2, 2], msg_refs) stored_ids = yield self.lookup_message_ids(service, seq_nums) self.assertEqual(['abc123'] * len(seq_nums), stored_ids) @inlineCallbacks def test_submit_csm_sar_single_part(self): """ If the content fits in a single message, all the multipart madness is avoided. """ service = yield self.get_service({'send_multipart_sar': True}) yield self.fake_smsc.bind() content = 'a' * 160 seq_numbers = yield service.submit_csm_sar( 'abc123', 'dest_addr', short_message=content) self.assertEqual(len(seq_numbers), 1) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(short_message(submit_sm_pdu), content) self.assertEqual(unpacked_pdu_opts(submit_sm_pdu), {}) @inlineCallbacks def test_submit_csm_udh(self): """ A long message can be sent in multiple PDUs with carefully handcrafted user data headers. """ service = yield self.get_service({'send_multipart_udh': True}) yield self.fake_smsc.bind() long_message = 'This is a long message.' * 20 seq_numbers = yield service.submit_csm_udh( 'abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) self.assertEqual(len(seq_numbers), 4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg = mandatory_parameters['short_message'] udh_hlen, udh_tag, udh_len, udh_ref, udh_tot, udh_seq = [ ord(octet) for octet in msg[:6]] self.assertEqual(5, udh_hlen) self.assertEqual(0, udh_tag) self.assertEqual(3, udh_len) msg_refs.append(udh_ref) self.assertEqual(4, udh_tot) self.assertEqual(i + 1, udh_seq) self.assertTrue(len(msg) <= 136) msg_parts.append(msg[6:]) self.assertEqual(0x40, mandatory_parameters['esm_class']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual(1, len(set(msg_refs))) stored_ids = yield self.lookup_message_ids(service, seq_numbers) self.assertEqual(['abc123'] * len(seq_numbers), stored_ids) @inlineCallbacks def test_submit_csm_udh_ref_num_limit(self): """ User data headers are crafted correctly when the generated reference number is larger than 0xFF. """ service = yield self.get_service({'send_multipart_udh': True}) yield self.fake_smsc.bind() # forward until we go past 0xFF yield self.set_sequence_number(service, 0x100) long_message = 'This is a long message.' * 20 seq_numbers = yield service.submit_csm_udh( 'abc123', 'dest_addr', short_message=long_message) pdus = yield self.fake_smsc.await_pdus(4) self.assertEqual(len(seq_numbers), 4) msg_parts = [] msg_refs = [] for i, sm in enumerate(pdus): mandatory_parameters = sm['body']['mandatory_parameters'] self.assertEqual('submit_sm', sm['header']['command_id']) msg = mandatory_parameters['short_message'] udh_hlen, udh_tag, udh_len, udh_ref, udh_tot, udh_seq = [ ord(octet) for octet in msg[:6]] self.assertEqual(5, udh_hlen) self.assertEqual(0, udh_tag) self.assertEqual(3, udh_len) msg_refs.append(udh_ref) self.assertEqual(4, udh_tot) self.assertEqual(i + 1, udh_seq) self.assertTrue(len(msg) <= 136) msg_parts.append(msg[6:]) self.assertEqual(0x40, mandatory_parameters['esm_class']) self.assertEqual(long_message, ''.join(msg_parts)) self.assertEqual(1, len(set(msg_refs))) stored_ids = yield self.lookup_message_ids(service, seq_numbers) self.assertEqual(['abc123'] * len(seq_numbers), stored_ids) @inlineCallbacks def test_submit_csm_udh_single_part(self): """ If the content fits in a single message, all the multipart madness is avoided. """ service = yield self.get_service({'send_multipart_udh': True}) yield self.fake_smsc.bind() content = 'a' * 160 seq_numbers = yield service.submit_csm_udh( 'abc123', 'dest_addr', short_message=content) self.assertEqual(len(seq_numbers), 1) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), 'submit_sm') self.assertEqual(short_message(submit_sm_pdu), content) self.assertEqual( submit_sm_pdu['body']['mandatory_parameters']['esm_class'], 0) @inlineCallbacks def test_pdu_cache_persistence(self): """ A cached PDU has an appropriate TTL and can be deleted. """ service = yield self.get_service() message_stash = service.message_stash config = service.get_config() pdu = SubmitSM(1337, short_message="foo") yield message_stash.cache_pdu("vumi0", pdu) ttl = yield message_stash.redis.ttl(pdu_key(1337)) self.assertTrue(0 < ttl <= config.submit_sm_expiry) pdu_data = yield message_stash.get_cached_pdu(1337) self.assertEqual(pdu_data.vumi_message_id, "vumi0") self.assertEqual(pdu_data.pdu.get_hex(), pdu.get_hex()) yield message_stash.delete_cached_pdu(1337) deleted_pdu_data = yield message_stash.get_cached_pdu(1337) self.assertEqual(deleted_pdu_data, None)
class MicaProcessorTestCase(VumiTestCase): def setUp(self): self.clock = Clock() self.fake_smsc = FakeSMSC() self.tx_helper = self.add_helper(TransportHelper(SmppTransceiverTransport)) self.default_config = { "transport_name": self.tx_helper.transport_name, "twisted_endpoint": self.fake_smsc.endpoint, "deliver_short_message_processor": ("vumi.transports.smpp.processors.mica." "DeliverShortMessageProcessor"), "submit_short_message_processor": ("vumi.transports.smpp.processors.mica." "SubmitShortMessageProcessor"), "system_id": "foo", "password": "******", "deliver_short_message_processor_config": {"data_coding_overrides": {0: "utf-8"}}, "submit_short_message_processor_config": { "submit_sm_encoding": "utf-16be", "submit_sm_data_coding": 8, "send_multipart_udh": True, }, } @inlineCallbacks def get_transport(self, config={}, bind=True): cfg = self.default_config.copy() transport = yield self.tx_helper.get_transport(cfg, start=False) transport.clock = self.clock yield transport.startWorker() self.clock.advance(0) if bind: yield self.fake_smsc.bind() returnValue(transport) def assert_udh_parts(self, pdus, texts, encoding): pdu_header = lambda pdu: short_message(pdu)[:6] pdu_text = lambda pdu: short_message(pdu)[6:].decode(encoding) udh_header = lambda i: "\x05\x00\x03\x03\x07" + chr(i) self.assertEqual( [(pdu_header(pdu), pdu_text(pdu)) for pdu in pdus], [(udh_header(i + 1), text) for i, text in enumerate(texts)], ) @inlineCallbacks def test_submit_sm_multipart_udh_ucs2(self): message = ( "A cup is a small, open container used for carrying and " "drinking drinks. It may be made of wood, plastic, glass, " "clay, metal, stone, china or other materials, and may have " "a stem, handles or other adornments. Cups are used for " "drinking across a wide range of cultures and social classes, " "and different styles of cups may be used for different liquids " "or in different situations. Cups have been used for thousands " "of years for the ...Reply 1 for more" ) yield self.get_transport() yield self.tx_helper.make_dispatch_outbound(message, to_addr="msisdn") pdus = yield self.fake_smsc.await_pdus(7) self.assert_udh_parts( pdus, [ ("A cup is a small, open container used" " for carrying and drinking d"), ("rinks. It may be made of wood, plastic," " glass, clay, metal, stone"), (", china or other materials, and may have" " a stem, handles or other"), (" adornments. Cups are used for drinking" " across a wide range of cu"), ("ltures and social classes, and different" " styles of cups may be us"), ("ed for different liquids or in different" " situations. Cups have be"), ("en used for thousands of years for the ...Reply 1 for more"), ], encoding="utf-16be", ) # utf-16be is close enough to UCS2 for pdu in pdus: self.assertTrue(len(short_message(pdu)) < 140) @inlineCallbacks def test_submit_and_deliver_ussd_new(self): session_identifier = 12345 yield self.get_transport() # Server delivers a USSD message to the Client pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter("ussd_service_op", "01") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess["content"], None) self.assertEqual(mess["to_addr"], "*123#") self.assertEqual(mess["transport_type"], "ussd") self.assertEqual(mess["session_event"], TransportUserMessage.SESSION_NEW) self.assertEqual( mess["transport_metadata"], {"session_info": {"session_identifier": 12345, "ussd_service_op": "01"}} ) @inlineCallbacks def test_deliver_sm_op_codes_new(self): session_identifier = 12345 yield self.get_transport() pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter("ussd_service_op", "01") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) [start] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(start["session_event"], TransportUserMessage.SESSION_NEW) @inlineCallbacks def test_deliver_sm_op_codes_resume(self): source_addr = "msisdn" session_identifier = 12345 vumi_session_identifier = make_vumi_session_identifier(source_addr, session_identifier) transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session(vumi_session_identifier, ussd_code="*123#") pdu = DeliverSM(1, short_message="", source_addr=source_addr) pdu.add_optional_parameter("ussd_service_op", "12") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) [resume] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(resume["session_event"], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_deliver_sm_op_codes_end(self): source_addr = "msisdn" session_identifier = 12345 vumi_session_identifier = make_vumi_session_identifier(source_addr, session_identifier) transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session(vumi_session_identifier, ussd_code="*123#") pdu = DeliverSM(1, short_message="", source_addr=source_addr) pdu.add_optional_parameter("ussd_service_op", "81") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) [end] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(end["session_event"], TransportUserMessage.SESSION_CLOSE) @inlineCallbacks def test_deliver_sm_unknown_op_code(self): session_identifier = 12345 yield self.get_transport() pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter("ussd_service_op", "01") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) pdu = DeliverSM(1, short_message="*123#") pdu.add_optional_parameter("ussd_service_op", "99") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) [start, unknown] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(unknown["session_event"], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_submit_sm_op_codes_resume(self): user_msisdn = "msisdn" session_identifier = 12345 yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_RESUME, transport_metadata={"session_info": {"session_identifier": session_identifier}}, to_addr=user_msisdn, ) resume = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(resume, "ussd_service_op"), "02") @inlineCallbacks def test_submit_sm_op_codes_close(self): user_msisdn = "msisdn" session_identifier = 12345 yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_CLOSE, transport_metadata={"session_info": {"session_identifier": session_identifier}}, to_addr=user_msisdn, ) close = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(close, "ussd_service_op"), "17") @inlineCallbacks def test_submit_and_deliver_ussd_continue(self): user_msisdn = "msisdn" session_identifier = 12345 vumi_session_identifier = make_vumi_session_identifier(user_msisdn, session_identifier) transport = yield self.get_transport() deliver_sm_processor = transport.deliver_sm_processor session_manager = deliver_sm_processor.session_manager yield session_manager.create_session(vumi_session_identifier, ussd_code="*123#") yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", transport_metadata={"session_info": {"session_identifier": session_identifier}}, to_addr=user_msisdn, ) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), "submit_sm") self.assertEqual(pdu_tlv(submit_sm_pdu, "ussd_service_op"), "02") self.assertEqual(pdu_tlv(submit_sm_pdu, "user_message_reference"), session_identifier) # Server delivers a USSD message to the Client pdu = DeliverSM(seq_no(submit_sm_pdu) + 1, short_message="reply!", source_addr=user_msisdn) # 0x12 is 'continue' pdu.add_optional_parameter("ussd_service_op", "12") pdu.add_optional_parameter("user_message_reference", session_identifier) yield self.fake_smsc.handle_pdu(pdu) [mess] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(mess["content"], "reply!") self.assertEqual(mess["transport_type"], "ussd") self.assertEqual(mess["to_addr"], "*123#") self.assertEqual(mess["session_event"], TransportUserMessage.SESSION_RESUME) @inlineCallbacks def test_submit_and_deliver_ussd_close(self): yield self.get_transport() session_identifier = 12345 yield self.tx_helper.make_dispatch_outbound( "hello world", transport_type="ussd", session_event=TransportUserMessage.SESSION_CLOSE, transport_metadata={"session_info": {"session_identifier": session_identifier}}, ) submit_sm_pdu = yield self.fake_smsc.await_pdu() self.assertEqual(command_id(submit_sm_pdu), "submit_sm") self.assertEqual(pdu_tlv(submit_sm_pdu, "ussd_service_op"), "17") self.assertEqual(pdu_tlv(submit_sm_pdu, "user_message_reference"), session_identifier) @inlineCallbacks def test_submit_sm_null_message(self): """ We can successfully send a message with null content. """ user_msisdn = "msisdn" session_identifier = 12345 yield self.get_transport() yield self.tx_helper.make_dispatch_outbound( None, transport_type="ussd", session_event=TransportUserMessage.SESSION_RESUME, transport_metadata={"session_info": {"session_identifier": session_identifier}}, to_addr=user_msisdn, ) resume = yield self.fake_smsc.await_pdu() self.assertEqual(pdu_tlv(resume, "ussd_service_op"), "02")
class TestEsmeProtocol(VumiTestCase): @inlineCallbacks def setUp(self): self.clock = Clock() self.persistence_helper = self.add_helper(PersistenceHelper()) self.redis = yield self.persistence_helper.get_redis_manager() self.fake_smsc = FakeSMSC(auto_accept=False) def get_protocol(self, config={}, bind_type='TRX', accept_connection=True): cfg = { 'transport_name': 'sphex_transport', 'twisted_endpoint': 'tcp:host=127.0.0.1:port=0', 'system_id': 'system_id', 'password': '******', 'smpp_bind_timeout': 30, } cfg.update(config) dummy_service = DummySmppService(self.clock, self.redis, cfg) factory = EsmeProtocolFactory(dummy_service, bind_type) proto_d = self.fake_smsc.endpoint.connect(factory) if accept_connection: self.fake_smsc.accept_connection() return proto_d def assertCommand(self, pdu, cmd_id, sequence_number=None, status=None, params={}): self.assertEqual(command_id(pdu), cmd_id) if sequence_number is not None: self.assertEqual(seq_no(pdu), sequence_number) if status is not None: self.assertEqual(command_status(pdu), status) pdu_params = {} if params: if 'body' not in pdu: raise Exception('Body does not have parameters.') mandatory_parameters = pdu['body']['mandatory_parameters'] for key in params: if key in mandatory_parameters: pdu_params[key] = mandatory_parameters[key] self.assertEqual(params, pdu_params) def lookup_message_ids(self, protocol, seq_nums): message_stash = protocol.service.message_stash lookup_func = message_stash.get_sequence_number_message_id return gatherResults([lookup_func(seq_num) for seq_num in seq_nums]) @inlineCallbacks def test_on_connection_made(self): connect_d = self.get_protocol(accept_connection=False) protocol = yield self.fake_smsc.await_connecting() self.assertEqual(protocol.state, EsmeProtocol.CLOSED_STATE) self.fake_smsc.accept_connection() protocol = yield connect_d # Same protocol. self.assertEqual(protocol.state, EsmeProtocol.OPEN_STATE) bind_pdu = yield self.fake_smsc.await_pdu() self.assertCommand( bind_pdu, 'bind_transceiver', sequence_number=1, params={ 'system_id': 'system_id', 'password': '******', }) @inlineCallbacks def test_drop_link(self): protocol = yield self.get_protocol() bind_pdu = yield self.fake_smsc.await_pdu() self.assertCommand(bind_pdu, 'bind_transceiver') self.assertFalse(protocol.is_bound()) self.assertEqual(protocol.state, EsmeProtocol.OPEN_STATE) self.clock.advance(protocol.config.smpp_bind_timeout + 1) unbind_pdu = yield self.fake_smsc.await_pdu() self.assertCommand(unbind_pdu, 'unbind') yield self.fake_smsc.send_pdu(UnbindResp(seq_no(unbind_pdu))) yield self.fake_smsc.await_disconnect() @inlineCallbacks def test_on_smpp_bind(self): protocol = yield self.get_protocol() yield self.fake_smsc.bind() self.assertEqual(protocol.state, EsmeProtocol.BOUND_STATE_TRX) self.assertTrue(protocol.is_bound()) self.assertTrue(protocol.enquire_link_call.running) @inlineCallbacks def test_handle_unbind(self): protocol = yield self.get_protocol() yield self.fake_smsc.bind() self.assertEqual(protocol.state, EsmeProtocol.BOUND_STATE_TRX) self.fake_smsc.send_pdu(Unbind(0)) pdu = yield self.fake_smsc.await_pdu() self.assertCommand( pdu, 'unbind_resp', sequence_number=0, status='ESME_ROK') # We don't change state here. self.assertEqual(protocol.state, EsmeProtocol.BOUND_STATE_TRX) @inlineCallbacks def test_on_submit_sm_resp(self): protocol = yield self.get_protocol() yield self.fake_smsc.bind() calls = [] protocol.on_submit_sm_resp = lambda *a: calls.append(a) yield self.fake_smsc.send_pdu(SubmitSMResp(0, message_id='foo')) self.assertEqual(calls, [(0, 'foo', 'ESME_ROK')]) @inlineCallbacks def test_deliver_sm(self): calls = [] protocol = yield self.get_protocol() protocol.handle_deliver_sm = lambda pdu: succeed(calls.append(pdu)) yield self.fake_smsc.bind() yield self.fake_smsc.send_pdu( DeliverSM(0, message_id='foo', short_message='bar')) [deliver_sm] = calls self.assertCommand(deliver_sm, 'deliver_sm', sequence_number=0) @inlineCallbacks def test_deliver_sm_fail(self): yield self.get_protocol() yield self.fake_smsc.bind() yield self.fake_smsc.send_pdu(DeliverSM( sequence_number=0, message_id='foo', data_coding=4, short_message='string with unknown data coding')) deliver_sm_resp = yield self.fake_smsc.await_pdu() self.assertCommand( deliver_sm_resp, 'deliver_sm_resp', sequence_number=0, status='ESME_RDELIVERYFAILURE') @inlineCallbacks def test_deliver_sm_fail_with_custom_error(self): yield self.get_protocol({ "deliver_sm_decoding_error": "ESME_RSYSERR" }) yield self.fake_smsc.bind() yield self.fake_smsc.send_pdu(DeliverSM( sequence_number=0, message_id='foo', data_coding=4, short_message='string with unknown data coding')) deliver_sm_resp = yield self.fake_smsc.await_pdu() self.assertCommand( deliver_sm_resp, 'deliver_sm_resp', sequence_number=0, status='ESME_RSYSERR') @inlineCallbacks def test_on_enquire_link(self): protocol = yield self.get_protocol() yield self.fake_smsc.bind() pdu = EnquireLink(0) protocol.dataReceived(pdu.get_bin()) enquire_link_resp = yield self.fake_smsc.await_pdu() self.assertCommand( enquire_link_resp, 'enquire_link_resp', sequence_number=0, status='ESME_ROK') @inlineCallbacks def test_on_enquire_link_resp(self): protocol = yield self.get_protocol() calls = [] protocol.handle_enquire_link_resp = calls.append yield self.fake_smsc.bind() [pdu] = calls # bind_transceiver is sequence_number 1 self.assertEqual(seq_no(pdu), 2) self.assertEqual(command_id(pdu), 'enquire_link_resp') @inlineCallbacks def test_enquire_link_no_response(self): self.fake_smsc.auto_unbind = False protocol = yield self.get_protocol() yield self.fake_smsc.bind() self.assertEqual(self.fake_smsc.connected, True) self.clock.advance(protocol.idle_timeout) [enquire_link_pdu, unbind_pdu] = yield self.fake_smsc.await_pdus(2) self.assertCommand(enquire_link_pdu, 'enquire_link') self.assertCommand(unbind_pdu, 'unbind') self.assertEqual(self.fake_smsc.connected, True) self.clock.advance(protocol.unbind_timeout) yield self.fake_smsc.await_disconnect() @inlineCallbacks def test_enquire_link_looping(self): self.fake_smsc.auto_unbind = False protocol = yield self.get_protocol() yield self.fake_smsc.bind() self.assertEqual(self.fake_smsc.connected, True) # Respond to a few enquire_link cycles. for i in range(5): self.clock.advance(protocol.idle_timeout - 1) pdu = yield self.fake_smsc.await_pdu() self.assertCommand(pdu, 'enquire_link') yield self.fake_smsc.respond_to_enquire_link(pdu) # Fail to respond, so we disconnect. self.clock.advance(protocol.idle_timeout - 1) pdu = yield self.fake_smsc.await_pdu() self.assertCommand(pdu, 'enquire_link') self.clock.advance(1) unbind_pdu = yield self.fake_smsc.await_pdu() self.assertCommand(unbind_pdu, 'unbind') yield self.fake_smsc.send_pdu( UnbindResp(seq_no(unbind_pdu))) yield self.fake_smsc.await_disconnect() @inlineCallbacks def test_submit_sm(self): protocol = yield self.get_protocol() yield self.fake_smsc.bind() seq_nums = yield protocol.submit_sm( 'abc123', 'dest_addr', short_message='foo') submit_sm = yield self.fake_smsc.await_pdu() self.assertCommand(submit_sm, 'submit_sm', params={ 'short_message': 'foo', }) stored_ids = yield self.lookup_message_ids(protocol, seq_nums) self.assertEqual(['abc123'], stored_ids) @inlineCallbacks def test_submit_sm_configured_parameters(self): protocol = yield self.get_protocol({ 'service_type': 'stype', 'source_addr_ton': 2, 'source_addr_npi': 2, 'dest_addr_ton': 2, 'dest_addr_npi': 2, 'registered_delivery': 0, }) yield self.fake_smsc.bind() seq_nums = yield protocol.submit_sm( 'abc123', 'dest_addr', short_message='foo') submit_sm = yield self.fake_smsc.await_pdu() self.assertCommand(submit_sm, 'submit_sm', params={ 'short_message': 'foo', 'service_type': 'stype', 'source_addr_ton': 'national', # replaced by unpack_pdu() 'source_addr_npi': 2, 'dest_addr_ton': 'national', # replaced by unpack_pdu() 'dest_addr_npi': 2, 'registered_delivery': 0, }) stored_ids = yield self.lookup_message_ids(protocol, seq_nums) self.assertEqual(['abc123'], stored_ids) @inlineCallbacks def test_query_sm(self): protocol = yield self.get_protocol() yield self.fake_smsc.bind() yield protocol.query_sm('foo', source_addr='bar') query_sm = yield self.fake_smsc.await_pdu() self.assertCommand(query_sm, 'query_sm', params={ 'message_id': 'foo', 'source_addr': 'bar', }) @inlineCallbacks def test_unbind(self): protocol = yield self.get_protocol() calls = [] protocol.handle_unbind_resp = calls.append yield self.fake_smsc.bind() yield protocol.unbind() unbind_pdu = yield self.fake_smsc.await_pdu() protocol.dataReceived(UnbindResp(seq_no(unbind_pdu)).get_bin()) [unbind_resp_pdu] = calls self.assertEqual(seq_no(unbind_resp_pdu), seq_no(unbind_pdu)) @inlineCallbacks def test_bind_transmitter(self): protocol = yield self.get_protocol(bind_type='TX') yield self.fake_smsc.bind() self.assertTrue(protocol.is_bound()) self.assertEqual(protocol.state, protocol.BOUND_STATE_TX) @inlineCallbacks def test_bind_receiver(self): protocol = yield self.get_protocol(bind_type='RX') yield self.fake_smsc.bind() self.assertTrue(protocol.is_bound()) self.assertEqual(protocol.state, protocol.BOUND_STATE_RX) @inlineCallbacks def test_partial_pdu_data_received(self): protocol = yield self.get_protocol() calls = [] protocol.handle_deliver_sm = calls.append yield self.fake_smsc.bind() deliver_sm = DeliverSM(1, short_message='foo') pdu = deliver_sm.get_bin() half = len(pdu) / 2 pdu_part1, pdu_part2 = pdu[:half], pdu[half:] yield self.fake_smsc.send_bytes(pdu_part1) self.assertEqual([], calls) yield self.fake_smsc.send_bytes(pdu_part2) [handled_pdu] = calls self.assertEqual(command_id(handled_pdu), 'deliver_sm') self.assertEqual(seq_no(handled_pdu), 1) self.assertEqual(short_message(handled_pdu), 'foo') @inlineCallbacks def test_unsupported_command_id(self): protocol = yield self.get_protocol() calls = [] protocol.on_unsupported_command_id = calls.append invalid_pdu = { 'header': { 'command_id': 'foo', } } protocol.on_pdu(invalid_pdu) self.assertEqual(calls, [invalid_pdu])