def test_course_info_to_ccxcon_ok(self, mock_post): """ Test for happy path """ mock_response = mock.Mock() mock_response.status_code = 201 mock_post.return_value = mock_response ccxconapi.course_info_to_ccxcon(self.course_key) assert mock_post.call_count == 1 k_args, k_kwargs = mock_post.call_args # no args used for the call assert k_args == tuple() assert k_kwargs.get('url') ==\ parse.urljoin(self.course.ccx_connector, ccxconapi.CCXCON_COURSEXS_URL) # second call with different status code mock_response.status_code = 200 mock_post.return_value = mock_response ccxconapi.course_info_to_ccxcon(self.course_key) assert mock_post.call_count == 2 k_args, k_kwargs = mock_post.call_args # no args used for the call assert k_args == tuple() assert k_kwargs.get('url') ==\ parse.urljoin(self.course.ccx_connector, ccxconapi.CCXCON_COURSEXS_URL)
def test_course_info_to_ccxcon_ok(self, mock_post): """ Test for happy path """ mock_response = mock.Mock() mock_response.status_code = 201 mock_post.return_value = mock_response ccxconapi.course_info_to_ccxcon(self.course_key) self.assertEqual(mock_post.call_count, 1) k_args, k_kwargs = mock_post.call_args # no args used for the call self.assertEqual(k_args, tuple()) self.assertEqual( k_kwargs.get('url'), six.moves.urllib.parse.urljoin(self.course.ccx_connector, ccxconapi.CCXCON_COURSEXS_URL)) # second call with different status code mock_response.status_code = 200 mock_post.return_value = mock_response ccxconapi.course_info_to_ccxcon(self.course_key) self.assertEqual(mock_post.call_count, 2) k_args, k_kwargs = mock_post.call_args # no args used for the call self.assertEqual(k_args, tuple()) self.assertEqual( k_kwargs.get('url'), six.moves.urllib.parse.urljoin(self.course.ccx_connector, ccxconapi.CCXCON_COURSEXS_URL))
def update_ccxcon(course_id, cur_retry=0): """ Pass through function to update course information on CCXCon. Takes care of retries in case of some specific exceptions. Args: course_id (str): string representing a course key cur_retry (int): integer representing the current task retry """ course_key = CourseKey.from_string(course_id) try: api.course_info_to_ccxcon(course_key) log.info(u'Course update to CCXCon returned no errors. Course key: %s', course_id) except (ConnectionError, HTTPError, RequestException, TooManyRedirects, api.CCXConnServerError) as exp: log.error( u'Course update to CCXCon failed for course_id %s with error: %s', course_id, exp) # in case the maximum amount of retries has not been reached, # insert another task delayed exponentially up to 5 retries if cur_retry < 5: update_ccxcon.apply_async( kwargs={ 'course_id': course_id, 'cur_retry': cur_retry + 1 }, countdown=10** cur_retry # number of seconds the task should be delayed ) log.info(u'Requeued celery task for course key %s ; retry # %s', course_id, cur_retry + 1)
def test_course_info_to_ccxcon_ok(self, mock_post): """ Test for happy path """ mock_response = mock.Mock() mock_response.status_code = 201 mock_post.return_value = mock_response ccxconapi.course_info_to_ccxcon(self.course_key) self.assertEqual(mock_post.call_count, 1) k_args, k_kwargs = mock_post.call_args # no args used for the call self.assertEqual(k_args, tuple()) self.assertEqual( k_kwargs.get('url'), urlparse.urljoin(self.course.ccx_connector, ccxconapi.CCXCON_COURSEXS_URL) ) # second call with different status code mock_response.status_code = 200 mock_post.return_value = mock_response ccxconapi.course_info_to_ccxcon(self.course_key) self.assertEqual(mock_post.call_count, 2) k_args, k_kwargs = mock_post.call_args # no args used for the call self.assertEqual(k_args, tuple()) self.assertEqual( k_kwargs.get('url'), urlparse.urljoin(self.course.ccx_connector, ccxconapi.CCXCON_COURSEXS_URL) )
def test_course_info_to_ccxcon_500_error(self, mock_post): """ Test for 500 error: a CCXConnServerError exception is raised """ mock_response = mock.Mock() mock_response.status_code = 500 mock_post.return_value = mock_response with pytest.raises(ccxconapi.CCXConnServerError): ccxconapi.course_info_to_ccxcon(self.course_key)
def test_course_info_to_ccxcon_500_error(self, mock_post): """ Test for 500 error: a CCXConnServerError exception is raised """ mock_response = mock.Mock() mock_response.status_code = 500 mock_post.return_value = mock_response with self.assertRaises(ccxconapi.CCXConnServerError): ccxconapi.course_info_to_ccxcon(self.course_key)
def test_course_info_to_ccxcon_invalid_ccx_connector(self, mock_post): """ Test for a course with invalid CCX connector URL """ # no connector at all self.course.ccx_connector = "" self.mstore.update_item(self.course, self.instructor.id) assert ccxconapi.course_info_to_ccxcon(self.course_key) is None assert mock_post.call_count == 0 # invalid url self.course.ccx_connector = "www.foo" self.mstore.update_item(self.course, self.instructor.id) assert ccxconapi.course_info_to_ccxcon(self.course_key) is None assert mock_post.call_count == 0
def test_course_info_to_ccxcon_invalid_ccx_connector(self, mock_post): """ Test for a course with invalid CCX connector URL """ # no connector at all self.course.ccx_connector = "" self.mstore.update_item(self.course, self.instructor.id) self.assertIsNone(ccxconapi.course_info_to_ccxcon(self.course_key)) self.assertEqual(mock_post.call_count, 0) # invalid url self.course.ccx_connector = "www.foo" self.mstore.update_item(self.course, self.instructor.id) self.assertIsNone(ccxconapi.course_info_to_ccxcon(self.course_key)) self.assertEqual(mock_post.call_count, 0)
def test_course_info_to_ccxcon_no_valid_course_key(self, mock_post): """ Test for an invalid course key """ missing_course_key = CourseKey.from_string('course-v1:FakeOrganization+CN999+CR-FALL99') self.assertIsNone(ccxconapi.course_info_to_ccxcon(missing_course_key)) self.assertEqual(mock_post.call_count, 0)
def test_course_info_to_ccxcon_no_ccx_enabled(self, mock_post): """ Test for a course without CCX enabled """ self.course.enable_ccx = False self.mstore.update_item(self.course, self.instructor.id) self.assertIsNone(ccxconapi.course_info_to_ccxcon(self.course_key)) self.assertEqual(mock_post.call_count, 0)
def test_course_info_to_ccxcon_no_config(self, mock_post): """ Test for course with ccx connector credentials not configured """ self.course.ccx_connector = "https://www.foo.com" self.mstore.update_item(self.course, self.instructor.id) self.assertIsNone(ccxconapi.course_info_to_ccxcon(self.course_key)) self.assertEqual(mock_post.call_count, 0)
def test_course_info_to_ccxcon_no_config(self, mock_post): """ Test for course with ccx connector credentials not configured """ self.course.ccx_connector = "https://www.foo.com" self.mstore.update_item(self.course, self.instructor.id) assert ccxconapi.course_info_to_ccxcon(self.course_key) is None assert mock_post.call_count == 0
def test_course_info_to_ccxcon_no_ccx_enabled(self, mock_post): """ Test for a course without CCX enabled """ self.course.enable_ccx = False self.mstore.update_item(self.course, self.instructor.id) assert ccxconapi.course_info_to_ccxcon(self.course_key) is None assert mock_post.call_count == 0
def test_course_info_to_ccxcon_other_status_codes(self, mock_post): """ Test for status codes different from >= 500 and 201: The called function doesn't raise any exception and simply returns None. """ mock_response = mock.Mock() for status_code in (204, 300, 304, 400, 404): mock_response.status_code = status_code mock_post.return_value = mock_response assert ccxconapi.course_info_to_ccxcon(self.course_key) is None
def test_course_info_to_ccxcon_other_status_codes(self, mock_post): """ Test for status codes different from >= 500 and 201: The called function doesn't raise any exception and simply returns None. """ mock_response = mock.Mock() for status_code in (204, 300, 304, 400, 404): mock_response.status_code = status_code mock_post.return_value = mock_response self.assertIsNone(ccxconapi.course_info_to_ccxcon(self.course_key))
def update_ccxcon(course_id, cur_retry=0): """ Pass through function to update course information on CCXCon. Takes care of retries in case of some specific exceptions. Args: course_id (str): string representing a course key cur_retry (int): integer representing the current task retry """ course_key = CourseKey.from_string(course_id) try: api.course_info_to_ccxcon(course_key) log.info('Course update to CCXCon returned no errors. Course key: %s', course_id) except (ConnectionError, HTTPError, RequestException, TooManyRedirects, api.CCXConnServerError) as exp: log.error('Course update to CCXCon failed for course_id %s with error: %s', course_id, exp) # in case the maximum amount of retries has not been reached, # insert another task delayed exponentially up to 5 retries if cur_retry < 5: update_ccxcon.apply_async( kwargs={'course_id': course_id, 'cur_retry': cur_retry + 1}, countdown=10 ** cur_retry # number of seconds the task should be delayed ) log.info('Requeued celery task for course key %s ; retry # %s', course_id, cur_retry + 1)