def zapier_subscription_pre_save(sender, instance, *args, **kwargs): """ Creates a repeater object corresponding to the type of trigger (form or case) """ if instance.pk: return if instance.event_name == EventTypes.NEW_FORM: repeater = FormRepeater(domain=instance.domain, url=instance.url, format='form_json', include_app_id_param=False, white_listed_form_xmlns=[instance.form_xmlns]) elif instance.event_name in CASE_TYPE_REPEATER_CLASS_MAP: repeater = CASE_TYPE_REPEATER_CLASS_MAP[instance.event_name]( domain=instance.domain, url=instance.url, format='case_json', white_listed_case_types=[instance.case_type], ) else: raise ImmediateHttpResponse( HttpBadRequest('The passed event type is not valid.')) repeater.save() instance.repeater_id = repeater.get_id
def zapier_subscription_pre_save(sender, instance, *args, **kwargs): """ Creates a repeater object corresponding to the type of trigger (form or case) """ if instance.pk: return if instance.event_name == EventTypes.NEW_FORM: repeater = FormRepeater( domain=instance.domain, url=instance.url, format='form_json', include_app_id_param=False, white_listed_form_xmlns=[instance.form_xmlns] ) elif instance.event_name in CASE_TYPE_REPEATER_CLASS_MAP: repeater = CASE_TYPE_REPEATER_CLASS_MAP[instance.event_name]( domain=instance.domain, url=instance.url, format='case_json', white_listed_case_types=[instance.case_type], ) else: raise ImmediateHttpResponse( HttpBadRequest('The passed event type is not valid.') ) repeater.save() instance.repeater_id = repeater.get_id
def test_clone_repeaters(self): from corehq.motech.repeaters.models import Repeater from corehq.motech.repeaters.models import CaseRepeater from corehq.motech.repeaters.models import FormRepeater self.assertEqual(0, len(Repeater.by_domain(self.new_domain))) case_repeater = CaseRepeater( domain=self.old_domain, url='case-repeater-url', ) case_repeater.save() self.addCleanup(case_repeater.delete) form_repeater = FormRepeater( domain=self.old_domain, url='form-repeater-url', ) form_repeater.save() self.addCleanup(form_repeater.delete) self.make_clone(include=['repeaters']) cloned_repeaters = Repeater.by_domain(self.new_domain) self.assertEqual(2, len(cloned_repeaters)) self.assertEqual({'CaseRepeater', 'FormRepeater'}, {repeater.doc_type for repeater in cloned_repeaters})
def test_get_list(self): # Add a form repeater and check that it comes back form_repeater = FormRepeater(domain=self.domain.name, url='http://example.com/forwarding/form') form_repeater.save() self.addCleanup(form_repeater.delete) backend_id = form_repeater._id response = self._assert_auth_get_resource(self.list_endpoint) self.assertEqual(response.status_code, 200) api_repeaters = json.loads(response.content)['objects'] self.assertEqual(len(api_repeaters), 1) self.assertEqual(api_repeaters[0]['id'], backend_id) self.assertEqual(api_repeaters[0]['url'], form_repeater.url) self.assertEqual(api_repeaters[0]['domain'], form_repeater.domain) self.assertEqual(api_repeaters[0]['type'], 'FormRepeater') # Add a case repeater and check that both come back case_repeater = CaseRepeater(domain=self.domain.name, url='http://example.com/forwarding/case') case_repeater.save() self.addCleanup(case_repeater.delete) backend_id = case_repeater._id response = self._assert_auth_get_resource(self.list_endpoint) self.assertEqual(response.status_code, 200) api_repeaters = json.loads(response.content)['objects'] self.assertEqual(len(api_repeaters), 2) api_case_repeater = [r for r in api_repeaters if r['type'] == 'CaseRepeater'][0] self.assertEqual(api_case_repeater['id'], case_repeater._id) self.assertEqual(api_case_repeater['url'], case_repeater.url) self.assertEqual(api_case_repeater['domain'], case_repeater.domain)
class IgnoreDocumentTest(BaseRepeaterTest): domain = 'ignore-doc' @classmethod def setUpClass(cls): super(IgnoreDocumentTest, cls).setUpClass() class NewFormGenerator(BasePayloadGenerator): format_name = 'new_format' format_label = 'XML' def get_payload(self, repeat_record, payload_doc): raise IgnoreDocument RegisterGenerator.get_collection(FormRepeater).add_new_format( NewFormGenerator) def setUp(self): super().setUp() self.connx = ConnectionSettings.objects.create( domain=self.domain, url='form-repeater-url', ) self.repeater = FormRepeater(domain=self.domain, connection_settings_id=self.connx.id, format='new_format') self.repeater.save() def tearDown(self): self.repeater.delete() self.connx.delete() FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) delete_all_repeat_records() @run_with_all_backends def test_ignore_document(self): """ When get_payload raises IgnoreDocument, fire should call update_success """ repeat_records = RepeatRecord.all(domain=self.domain, ) for repeat_record_ in repeat_records: repeat_record_.fire() self.assertIsNone(repeat_record_.next_check) self.assertTrue(repeat_record_.succeeded)
class IgnoreDocumentTest(BaseRepeaterTest): @classmethod def setUpClass(cls): super(IgnoreDocumentTest, cls).setUpClass() class NewFormGenerator(BasePayloadGenerator): format_name = 'new_format' format_label = 'XML' def get_payload(self, repeat_record, payload_doc): raise IgnoreDocument RegisterGenerator.get_collection(FormRepeater).add_new_format(NewFormGenerator) def setUp(self): super(IgnoreDocumentTest, self).setUp() self.domain = "test-domain" create_domain(self.domain) self.repeater = FormRepeater( domain=self.domain, url='form-repeater-url', format='new_format' ) self.repeater.save() def tearDown(self): self.repeater.delete() delete_all_repeat_records() super(IgnoreDocumentTest, self).tearDown() @run_with_all_backends def test_ignore_document(self): """ When get_payload raises IgnoreDocument, fire should call update_success """ repeat_records = RepeatRecord.all( domain=self.domain, ) for repeat_record_ in repeat_records: repeat_record_.fire() self.assertIsNone(repeat_record_.next_check) self.assertTrue(repeat_record_.succeeded)
class IgnoreDocumentTest(BaseRepeaterTest): @classmethod def setUpClass(cls): super(IgnoreDocumentTest, cls).setUpClass() class NewFormGenerator(BasePayloadGenerator): format_name = 'new_format' format_label = 'XML' def get_payload(self, repeat_record, payload_doc): raise IgnoreDocument RegisterGenerator.get_collection(FormRepeater).add_new_format(NewFormGenerator) def setUp(self): super(IgnoreDocumentTest, self).setUp() self.domain = "test-domain" create_domain(self.domain) self.repeater = FormRepeater( domain=self.domain, url='form-repeater-url', format='new_format' ) self.repeater.save() def tearDown(self): self.repeater.delete() delete_all_repeat_records() super(IgnoreDocumentTest, self).tearDown() @run_with_all_backends def test_ignore_document(self): """ When get_payload raises IgnoreDocument, fire should call update_success """ repeat_records = RepeatRecord.all( domain=self.domain, ) for repeat_record_ in repeat_records: repeat_record_.fire() self.assertIsNone(repeat_record_.next_check) self.assertTrue(repeat_record_.succeeded)
class TestRepeatRecordsUpdateRepeater(TestCase): def setUp(self): self.rep = FormRepeater( domain=DOMAIN, url='https://server.example.com/api/', ) self.rep.save() def tearDown(self): self.rep.delete() def test_success(self): one_minute = timedelta(minutes=1) rec = RepeatRecord( domain=DOMAIN, repeater_id=self.rep.get_id, ) attempt = RepeatRecordAttempt( datetime=datetime.utcnow() - one_minute, success_response='w00t', succeeded=True, ) rec.add_attempt(attempt) self.assertEqual(rec.repeater.last_success_at, attempt.datetime) self.assertEqual(rec.repeater.failure_streak, 0) def test_failure(self): one_minute = timedelta(minutes=1) rec = RepeatRecord( domain=DOMAIN, repeater_id=self.rep.get_id, ) attempt = RepeatRecordAttempt( datetime=datetime.utcnow() - one_minute, failure_reason='oops', succeeded=False, ) rec.add_attempt(attempt) self.assertIsNone(rec.repeater.last_success_at) self.assertEqual(rec.repeater.failure_streak, 1)
def test_clone_repeaters(self): from corehq.motech.repeaters.models import Repeater from corehq.motech.repeaters.models import CaseRepeater from corehq.motech.repeaters.models import FormRepeater from custom.enikshay.integrations.nikshay.repeaters import NikshayRegisterPatientRepeater self.assertEqual(0, len(Repeater.by_domain(self.new_domain))) self.assertEqual( 0, len(NikshayRegisterPatientRepeater.by_domain(self.new_domain))) case_repeater = CaseRepeater( domain=self.old_domain, url='case-repeater-url', ) case_repeater.save() self.addCleanup(case_repeater.delete) form_repeater = FormRepeater( domain=self.old_domain, url='form-repeater-url', ) form_repeater.save() self.addCleanup(form_repeater.delete) custom_repeater = NikshayRegisterPatientRepeater( domain=self.old_domain, url='99dots') custom_repeater.save() self.addCleanup(custom_repeater.delete) self.make_clone(include=['repeaters']) cloned_repeaters = Repeater.by_domain(self.new_domain) self.assertEqual(3, len(cloned_repeaters)) self.assertEqual( {'CaseRepeater', 'FormRepeater', 'NikshayRegisterPatientRepeater'}, {repeater.doc_type for repeater in cloned_repeaters}) # test cache clearing cloned_niksay_repeaters = NikshayRegisterPatientRepeater.by_domain( self.new_domain) self.assertEqual(1, len(cloned_niksay_repeaters))
class RepeaterTest(BaseRepeaterTest): def setUp(self): super(RepeaterTest, self).setUp() self.domain = "test-domain" create_domain(self.domain) self.case_repeater = CaseRepeater( domain=self.domain, url='case-repeater-url', ) self.case_repeater.save() self.form_repeater = FormRepeater( domain=self.domain, url='form-repeater-url', ) self.form_repeater.save() self.log = [] self.post_xml(self.xform_xml, self.domain) def tearDown(self): self.case_repeater.delete() self.form_repeater.delete() FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) delete_all_repeat_records() super(RepeaterTest, self).tearDown() @run_with_all_backends def test_skip_device_logs(self): devicelog_xml = XFORM_XML_TEMPLATE.format(DEVICE_LOG_XMLNS, USER_ID, '1234', '') self.post_xml(devicelog_xml, self.domain) repeat_records = RepeatRecord.all(domain=self.domain) for repeat_record in repeat_records: self.assertNotEqual(repeat_record.payload_id, '1234') @run_with_all_backends def test_skip_duplicates(self): """ Ensure that submitting a duplicate form does not create extra RepeatRecords """ self.assertEqual(len(RepeatRecord.all()), 2) # this form is already submitted during setUp so a second submission should be a duplicate form = self.post_xml(self.xform_xml, self.domain).xform self.assertTrue(form.is_duplicate) self.assertEqual(len(RepeatRecord.all()), 2) @run_with_all_backends def test_repeater_failed_sends(self): """ This tests records that fail are requeued later """ def now(): return datetime.utcnow() repeat_records = RepeatRecord.all(domain=self.domain, due_before=now()) self.assertEqual(len(repeat_records), 2) for repeat_record in repeat_records: with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse( status_code=404, reason='Not Found')) as mock_post: repeat_record.fire() self.assertEqual(mock_post.call_count, 1) next_check_time = now() + timedelta(minutes=60) repeat_records = RepeatRecord.all( domain=self.domain, due_before=now() + timedelta(minutes=15), ) self.assertEqual(len(repeat_records), 0) repeat_records = RepeatRecord.all( domain=self.domain, due_before=next_check_time, ) self.assertEqual(len(repeat_records), 2) @run_with_all_backends def test_update_failure_next_check(self): now = datetime.utcnow() record = RepeatRecord(domain=self.domain, next_check=now) self.assertIsNone(record.last_checked) attempt = record.make_set_next_try_attempt(None) record.add_attempt(attempt) self.assertTrue(record.last_checked > now) self.assertEqual(record.next_check, record.last_checked + MIN_RETRY_WAIT) @run_with_all_backends def test_repeater_successful_send(self): repeat_records = RepeatRecord.all(domain=self.domain, due_before=datetime.utcnow()) for repeat_record in repeat_records: with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse( status_code=200, reason='No Reason')) as mock_post: repeat_record.fire() self.assertEqual(mock_post.call_count, 1) mock_post.assert_any_call( repeat_record.get_payload(), repeat_record.repeater.get_url(repeat_record), headers=repeat_record.repeater.get_headers(repeat_record), timeout=POST_TIMEOUT, auth=repeat_record.repeater.get_auth(), verify=repeat_record.repeater.verify, ) # The following is pretty fickle and depends on which of # - corehq.motech.repeaters.signals # - casexml.apps.case.signals # gets loaded first. # This is deterministic but easily affected by minor code changes repeat_records = RepeatRecord.all( domain=self.domain, due_before=datetime.utcnow(), ) for repeat_record in repeat_records: self.assertEqual(repeat_record.succeeded, True) self.assertEqual(repeat_record.next_check, None) self.assertEqual(len(self.repeat_records(self.domain)), 0) self.post_xml(self.update_xform_xml, self.domain) self.assertEqual(len(self.repeat_records(self.domain)), 2) @run_with_all_backends def test_check_repeat_records(self): self.assertEqual(len(RepeatRecord.all()), 2) with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 2) with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) @run_with_all_backends def test_repeat_record_status_check(self): self.assertEqual(len(RepeatRecord.all()), 2) # Do not trigger cancelled records for repeat_record in RepeatRecord.all(): repeat_record.cancelled = True repeat_record.save() with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) # trigger force send records if not cancelled and tries not exhausted for repeat_record in RepeatRecord.all(): with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse(status_code=200, reason='')) as mock_fire: repeat_record.fire(force_send=True) self.assertEqual(mock_fire.call_count, 1) # all records should be in SUCCESS state after force try for repeat_record in RepeatRecord.all(): self.assertEqual(repeat_record.state, RECORD_SUCCESS_STATE) self.assertEqual(repeat_record.overall_tries, 1) # not trigger records succeeded triggered after cancellation with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) for repeat_record in RepeatRecord.all(): self.assertEqual(repeat_record.state, RECORD_SUCCESS_STATE) @run_with_all_backends def test_process_repeat_record_locking(self): self.assertEqual(len(RepeatRecord.all()), 2) with patch('corehq.motech.repeaters.tasks.process_repeat_record' ) as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 2) with patch('corehq.motech.repeaters.tasks.process_repeat_record' ) as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 0) records = RepeatRecord.all() # Saving should unlock them again by changing the rev for record in records: record.save() with patch('corehq.motech.repeaters.tasks.process_repeat_record' ) as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 2) @run_with_all_backends def test_automatic_cancel_repeat_record(self): repeat_record = self.case_repeater.register( CaseAccessors(self.domain).get_case(CASE_ID)) repeat_record.overall_tries = 1 with patch('corehq.motech.repeaters.models.simple_post', side_effect=Exception('Boom!')): repeat_record.fire() self.assertEqual(2, repeat_record.overall_tries) with patch('corehq.motech.repeaters.models.simple_post', side_effect=Exception('Boom!')): repeat_record.fire() self.assertEqual(True, repeat_record.cancelled) repeat_record.requeue() self.assertEqual(0, repeat_record.overall_tries) self.assertNotEqual(None, repeat_record.next_check)
class RepeaterTest(BaseRepeaterTest): def setUp(self): super(RepeaterTest, self).setUp() self.domain = "test-domain" create_domain(self.domain) self.case_repeater = CaseRepeater( domain=self.domain, url='case-repeater-url', ) self.case_repeater.save() self.form_repeater = FormRepeater( domain=self.domain, url='form-repeater-url', ) self.form_repeater.save() self.log = [] with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: self.post_xml(self.xform_xml, self.domain) self.initial_fire_call_count = mock_fire.call_count def tearDown(self): self.case_repeater.delete() self.form_repeater.delete() FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) delete_all_repeat_records() super(RepeaterTest, self).tearDown() def repeat_records(self): return super(RepeaterTest, self).repeat_records(self.domain) @run_with_all_backends def test_skip_device_logs(self): devicelog_xml = XFORM_XML_TEMPLATE.format(DEVICE_LOG_XMLNS, USER_ID, '1234', '') self.post_xml(devicelog_xml, self.domain) for repeat_record in self.repeat_records(): self.assertNotEqual(repeat_record.payload_id, '1234') @run_with_all_backends def test_skip_duplicates(self): """ Ensure that submitting a duplicate form does not create extra RepeatRecords """ self.assertEqual(len(self.repeat_records()), 2) # this form is already submitted during setUp so a second submission should be a duplicate form = self.post_xml(self.xform_xml, self.domain).xform self.assertTrue(form.is_duplicate) self.assertEqual(len(self.repeat_records()), 2) @run_with_all_backends def test_repeater_failed_sends(self): """ This tests records that fail are requeued later """ def now(): return datetime.utcnow() repeat_records = self.repeat_records() self.assertEqual(len(repeat_records), 2) for repeat_record in repeat_records: with patch( 'corehq.motech.repeaters.models.simple_post', return_value=MockResponse(status_code=404, reason='Not Found')) as mock_post: repeat_record.fire() self.assertEqual(mock_post.call_count, 1) # Enqueued repeat records have next_check incremented by 48 hours next_check_time = now() + timedelta(minutes=60) + timedelta(hours=48) repeat_records = RepeatRecord.all( domain=self.domain, due_before=now() + timedelta(minutes=15), ) self.assertEqual(len(repeat_records), 0) repeat_records = RepeatRecord.all( domain=self.domain, due_before=next_check_time, ) self.assertEqual(len(repeat_records), 2) @run_with_all_backends def test_update_failure_next_check(self): now = datetime.utcnow() record = RepeatRecord(domain=self.domain, next_check=now) self.assertIsNone(record.last_checked) attempt = record.make_set_next_try_attempt(None) record.add_attempt(attempt) self.assertTrue(record.last_checked > now) self.assertEqual(record.next_check, record.last_checked + MIN_RETRY_WAIT) @run_with_all_backends def test_repeater_successful_send(self): repeat_records = self.repeat_records() for repeat_record in repeat_records: with patch( 'corehq.motech.repeaters.models.simple_post', return_value=MockResponse(status_code=200, reason='No Reason')) as mock_post: repeat_record.fire() self.assertEqual(mock_post.call_count, 1) mock_post.assert_any_call( repeat_record.get_payload(), repeat_record.repeater.get_url(repeat_record), headers=repeat_record.repeater.get_headers(repeat_record), timeout=POST_TIMEOUT, auth=repeat_record.repeater.get_auth(), verify=repeat_record.repeater.verify, ) # The following is pretty fickle and depends on which of # - corehq.motech.repeaters.signals # - casexml.apps.case.signals # gets loaded first. # This is deterministic but easily affected by minor code changes repeat_records = self.repeat_records() for repeat_record in repeat_records: self.assertEqual(repeat_record.succeeded, True) self.assertEqual(repeat_record.next_check, None) self.assertEqual(len(self.repeat_records()), 0) self.post_xml(self.update_xform_xml, self.domain) self.assertEqual(len(self.repeat_records()), 2) @run_with_all_backends def test_check_repeat_records(self): self.assertEqual(len(self.repeat_records()), 2) self.assertEqual(self.initial_fire_call_count, 2) with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) @run_with_all_backends def test_repeat_record_status_check(self): self.assertEqual(len(self.repeat_records()), 2) # Do not trigger cancelled records for repeat_record in self.repeat_records(): repeat_record.cancelled = True repeat_record.save() with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) # trigger force send records if not cancelled and tries not exhausted for repeat_record in self.repeat_records(): with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse(status_code=200, reason='') ) as mock_fire: repeat_record.fire(force_send=True) self.assertEqual(mock_fire.call_count, 1) # all records should be in SUCCESS state after force try for repeat_record in self.repeat_records(): self.assertEqual(repeat_record.state, RECORD_SUCCESS_STATE) self.assertEqual(repeat_record.overall_tries, 1) # not trigger records succeeded triggered after cancellation with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) for repeat_record in self.repeat_records(): self.assertEqual(repeat_record.state, RECORD_SUCCESS_STATE) @run_with_all_backends def test_process_repeat_record_locking(self): self.assertEqual(len(self.repeat_records()), 2) with patch('corehq.motech.repeaters.tasks.process_repeat_record') as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 0) for record in self.repeat_records(): # Resetting next_check should allow them to be requeued record.next_check = datetime.utcnow() record.save() with patch('corehq.motech.repeaters.tasks.process_repeat_record') as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 2) @run_with_all_backends def test_automatic_cancel_repeat_record(self): repeat_record = self.case_repeater.register(CaseAccessors(self.domain).get_case(CASE_ID)) self.assertEqual(1, repeat_record.overall_tries) with patch('corehq.motech.repeaters.models.simple_post', side_effect=Exception('Boom!')): for __ in range(repeat_record.max_possible_tries - repeat_record.overall_tries): repeat_record.fire() self.assertEqual(True, repeat_record.cancelled) repeat_record.requeue() self.assertEqual(0, repeat_record.overall_tries) self.assertNotEqual(None, repeat_record.next_check)
class RepeaterTest(BaseRepeaterTest): domain = "repeater-test" def setUp(self): super(RepeaterTest, self).setUp() self.case_connx = ConnectionSettings.objects.create( domain=self.domain, url='case-repeater-url', ) self.case_repeater = CaseRepeater( domain=self.domain, connection_settings_id=self.case_connx.id, ) self.case_repeater.save() self.form_connx = ConnectionSettings.objects.create( domain=self.domain, url='form-repeater-url', ) self.form_repeater = FormRepeater( domain=self.domain, connection_settings_id=self.form_connx.id, ) self.form_repeater.save() self.log = [] with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse(status_code=500, reason="Borked")) as mock_fire: self.post_xml(self.xform_xml, self.domain) self.initial_fire_call_count = mock_fire.call_count def tearDown(self): self.case_repeater.delete() self.case_connx.delete() self.form_repeater.delete() self.form_connx.delete() FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) delete_all_repeat_records() super(RepeaterTest, self).tearDown() def repeat_records(self): return super(RepeaterTest, self).repeat_records(self.domain) @run_with_all_backends def test_skip_device_logs(self): devicelog_xml = XFORM_XML_TEMPLATE.format(DEVICE_LOG_XMLNS, USER_ID, '1234', '') self.post_xml(devicelog_xml, self.domain) for repeat_record in self.repeat_records(): self.assertNotEqual(repeat_record.payload_id, '1234') @run_with_all_backends def test_skip_duplicates(self): """ Ensure that submitting a duplicate form does not create extra RepeatRecords """ self.assertEqual(len(self.repeat_records()), 2) # this form is already submitted during setUp so a second submission should be a duplicate form = self.post_xml(self.xform_xml, self.domain).xform self.assertTrue(form.is_duplicate) self.assertEqual(len(self.repeat_records()), 2) @run_with_all_backends def test_repeater_failed_sends(self): """ This tests records that fail are requeued later """ def now(): return datetime.utcnow() repeat_records = self.repeat_records() self.assertEqual(len(repeat_records), 2) for repeat_record in repeat_records: with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse( status_code=404, reason='Not Found')) as mock_post: repeat_record.fire() self.assertEqual(mock_post.call_count, 1) # Enqueued repeat records have next_check incremented by 48 hours next_check_time = now() + timedelta(minutes=60) + timedelta(hours=48) repeat_records = RepeatRecord.all( domain=self.domain, due_before=now() + timedelta(minutes=15), ) self.assertEqual(len(repeat_records), 0) repeat_records = RepeatRecord.all( domain=self.domain, due_before=next_check_time, ) self.assertEqual(len(repeat_records), 2) @run_with_all_backends def test_update_failure_next_check(self): now = datetime.utcnow() record = RepeatRecord( domain=self.domain, repeater_id=self.case_repeater.get_id, next_check=now, ) self.assertIsNone(record.last_checked) attempt = record.make_set_next_try_attempt(None) record.add_attempt(attempt) self.assertTrue(record.last_checked > now) self.assertEqual(record.next_check, record.last_checked + MIN_RETRY_WAIT) @run_with_all_backends def test_repeater_successful_send(self): repeat_records = self.repeat_records() for repeat_record in repeat_records: with patch('corehq.motech.repeaters.models.simple_post') as mock_post, \ patch.object(ConnectionSettings, 'get_auth_manager') as mock_manager: mock_post.return_value.status_code = 200 mock_manager.return_value = 'MockAuthManager' repeat_record.fire() self.assertEqual(mock_post.call_count, 1) mock_post.assert_called_with( self.domain, repeat_record.repeater.get_url(repeat_record), repeat_record.get_payload(), headers=repeat_record.repeater.get_headers(repeat_record), auth_manager='MockAuthManager', verify=repeat_record.repeater.verify, notify_addresses=[], payload_id=repeat_record.payload_id, ) # The following is pretty fickle and depends on which of # - corehq.motech.repeaters.signals # - casexml.apps.case.signals # gets loaded first. # This is deterministic but easily affected by minor code changes repeat_records = self.repeat_records() for repeat_record in repeat_records: self.assertEqual(repeat_record.succeeded, True) self.assertEqual(repeat_record.next_check, None) self.assertEqual(len(self.repeat_records()), 0) self.post_xml(self.update_xform_xml, self.domain) self.assertEqual(len(self.repeat_records()), 2) @run_with_all_backends def test_check_repeat_records(self): self.assertEqual(len(self.repeat_records()), 2) self.assertEqual(self.initial_fire_call_count, 2) with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) @run_with_all_backends def test_repeat_record_status_check(self): self.assertEqual(len(self.repeat_records()), 2) # Do not trigger cancelled records for repeat_record in self.repeat_records(): repeat_record.cancelled = True repeat_record.save() with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) # trigger force send records if not cancelled and tries not exhausted for repeat_record in self.repeat_records(): with patch('corehq.motech.repeaters.models.simple_post', return_value=MockResponse(status_code=200, reason='')) as mock_fire: repeat_record.fire(force_send=True) self.assertEqual(mock_fire.call_count, 1) # all records should be in SUCCESS state after force try for repeat_record in self.repeat_records(): self.assertEqual(repeat_record.state, RECORD_SUCCESS_STATE) self.assertEqual(repeat_record.overall_tries, 1) # not trigger records succeeded triggered after cancellation with patch('corehq.motech.repeaters.models.simple_post') as mock_fire: check_repeaters() self.assertEqual(mock_fire.call_count, 0) for repeat_record in self.repeat_records(): self.assertEqual(repeat_record.state, RECORD_SUCCESS_STATE) @run_with_all_backends def test_retry_process_repeat_record_locking(self): self.assertEqual(len(self.repeat_records()), 2) with patch('corehq.motech.repeaters.tasks.retry_process_repeat_record' ) as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 0) for record in self.repeat_records(): # Resetting next_check should allow them to be requeued record.next_check = datetime.utcnow() record.save() with patch('corehq.motech.repeaters.tasks.retry_process_repeat_record' ) as mock_process: check_repeaters() self.assertEqual(mock_process.delay.call_count, 2) @run_with_all_backends def test_automatic_cancel_repeat_record(self): repeat_record = self.case_repeater.register( CaseAccessors(self.domain).get_case(CASE_ID)) self.assertEqual(1, repeat_record.overall_tries) with patch('corehq.motech.repeaters.models.simple_post', side_effect=Exception('Boom!')): for __ in range(repeat_record.max_possible_tries - repeat_record.overall_tries): repeat_record.fire() self.assertEqual(True, repeat_record.cancelled) repeat_record.requeue() self.assertEqual(0, repeat_record.overall_tries) self.assertNotEqual(None, repeat_record.next_check)