feat: fixed one test
* fixed one test to accommodate a slightly modified workflow * allowed the automated linter to match current standards Note: as part of this process, I realized a lot of the tests in `programs/tests/test_tasks.py` are somewhat problematic. Some aren't real unit tests (allow calls to something other than the system under test), at least one for that reason it won't run on local at all, and some mock the wrong part of the system or just don't match current flow. I'm not modifying them as part of this, but they should be looked at. FIXES: APER-3146
This commit is contained in:
@@ -482,7 +482,6 @@ def award_course_certificate(self, username, course_run_key):
|
||||
reason=error_msg,
|
||||
countdown=countdown,
|
||||
)
|
||||
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_run_key)
|
||||
try:
|
||||
@@ -496,7 +495,8 @@ def award_course_certificate(self, username, course_run_key):
|
||||
# Get the cert for the course key and username if it's both passing and available in professional/verified
|
||||
try:
|
||||
certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=user.id, course_id=course_key
|
||||
user=user.id,
|
||||
course_id=course_key,
|
||||
)
|
||||
except GeneratedCertificate.DoesNotExist:
|
||||
LOGGER.exception(
|
||||
|
||||
@@ -19,31 +19,23 @@ from django.test import TestCase, override_settings
|
||||
from edx_rest_api_client.auth import SuppliedJwtAuth
|
||||
from requests.exceptions import HTTPError
|
||||
from testfixtures import LogCapture
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
|
||||
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.certificates.tests.factories import (
|
||||
CertificateDateOverrideFactory,
|
||||
GeneratedCertificateFactory,
|
||||
)
|
||||
from lms.djangoapps.certificates.tests.factories import CertificateDateOverrideFactory, GeneratedCertificateFactory
|
||||
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import (
|
||||
CourseOverviewFactory,
|
||||
)
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin
|
||||
from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory
|
||||
from openedx.core.djangoapps.programs import tasks
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import (
|
||||
SiteConfigurationFactory,
|
||||
SiteFactory,
|
||||
)
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CREDENTIALS_INTERNAL_SERVICE_URL = "https://credentials.example.com"
|
||||
TASKS_MODULE = "openedx.core.djangoapps.programs.tasks"
|
||||
CREDENTIALS_INTERNAL_SERVICE_URL = 'https://credentials.example.com'
|
||||
TASKS_MODULE = 'openedx.core.djangoapps.programs.tasks'
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@@ -57,101 +49,84 @@ class GetAwardedCertificateProgramsTestCase(TestCase):
|
||||
Helper to make dummy results from the credentials API
|
||||
"""
|
||||
result = {
|
||||
"id": 1,
|
||||
"username": "dummy-username",
|
||||
"credential": {
|
||||
"credential_id": None,
|
||||
"program_uuid": None,
|
||||
'id': 1,
|
||||
'username': 'dummy-username',
|
||||
'credential': {
|
||||
'credential_id': None,
|
||||
'program_uuid': None,
|
||||
},
|
||||
"status": "dummy-status",
|
||||
"uuid": "dummy-uuid",
|
||||
"certificate_url": "http://credentials.edx.org/credentials/dummy-uuid/",
|
||||
'status': 'dummy-status',
|
||||
'uuid': 'dummy-uuid',
|
||||
'certificate_url': 'http://credentials.edx.org/credentials/dummy-uuid/'
|
||||
}
|
||||
result.update(**kwargs)
|
||||
return result
|
||||
|
||||
@mock.patch(TASKS_MODULE + ".get_credentials")
|
||||
@mock.patch(TASKS_MODULE + '.get_credentials')
|
||||
def test_get_certified_programs(self, mock_get_credentials):
|
||||
"""
|
||||
Ensure the API is called and results handled correctly.
|
||||
"""
|
||||
student = UserFactory(username="test-username")
|
||||
student = UserFactory(username='test-username')
|
||||
mock_get_credentials.return_value = [
|
||||
self.make_credential_result(
|
||||
status="awarded", credential={"program_uuid": 1}
|
||||
),
|
||||
self.make_credential_result(status='awarded', credential={'program_uuid': 1}),
|
||||
]
|
||||
|
||||
result = tasks.get_certified_programs(student)
|
||||
assert mock_get_credentials.call_args[0] == (student,)
|
||||
assert mock_get_credentials.call_args[1] == {"credential_type": "program"}
|
||||
assert mock_get_credentials.call_args[1] == {'credential_type': 'program'}
|
||||
assert result == [1]
|
||||
|
||||
def test_data_retrieval_raise_on_error(self, mock_raise):
|
||||
"""
|
||||
Verify that when raise_on_error is set and the API call gives an error response,
|
||||
get_certified_programs reraises HttpError.
|
||||
"""
|
||||
mock_raise.return_value = HTTPError("An Error occured")
|
||||
student = UserFactory(username="test-username")
|
||||
with self.assertRaises(HTTPError):
|
||||
tasks.get_certified_programs(student, raise_on_error=True)
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class AwardProgramCertificateTestCase(TestCase):
|
||||
"""
|
||||
Test the award_program_certificate function
|
||||
"""
|
||||
|
||||
@httpretty.activate
|
||||
@mock.patch("openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url")
|
||||
@mock.patch('openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url')
|
||||
def test_award_program_certificate(self, mock_get_api_base_url):
|
||||
"""
|
||||
Ensure the correct API call gets made
|
||||
"""
|
||||
mock_get_api_base_url.return_value = "http://test-server/"
|
||||
student = UserFactory(username="test-username", email="test-email@email.com")
|
||||
mock_get_api_base_url.return_value = 'http://test-server/'
|
||||
student = UserFactory(username='test-username', email='test-email@email.com')
|
||||
|
||||
test_client = requests.Session()
|
||||
test_client.auth = SuppliedJwtAuth("test-token")
|
||||
test_client.auth = SuppliedJwtAuth('test-token')
|
||||
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
"http://test-server/credentials/",
|
||||
'http://test-server/credentials/',
|
||||
)
|
||||
|
||||
tasks.award_program_certificate(
|
||||
test_client, student, 123, datetime(2010, 5, 30)
|
||||
)
|
||||
tasks.award_program_certificate(test_client, student, 123, datetime(2010, 5, 30))
|
||||
|
||||
expected_body = {
|
||||
"username": student.username,
|
||||
"lms_user_id": student.id,
|
||||
"credential": {
|
||||
"program_uuid": 123,
|
||||
"type": tasks.PROGRAM_CERTIFICATE,
|
||||
'username': student.username,
|
||||
'lms_user_id': student.id,
|
||||
'credential': {
|
||||
'program_uuid': 123,
|
||||
'type': tasks.PROGRAM_CERTIFICATE,
|
||||
},
|
||||
"attributes": [
|
||||
'attributes': [
|
||||
{
|
||||
"name": "visible_date",
|
||||
"value": "2010-05-30T00:00:00Z",
|
||||
'name': 'visible_date',
|
||||
'value': '2010-05-30T00:00:00Z',
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
last_request_body = httpretty.last_request().body.decode("utf-8")
|
||||
last_request_body = httpretty.last_request().body.decode('utf-8')
|
||||
assert json.loads(last_request_body) == expected_body
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@ddt.ddt
|
||||
@mock.patch(TASKS_MODULE + ".award_program_certificate")
|
||||
@mock.patch(TASKS_MODULE + ".get_certified_programs")
|
||||
@mock.patch(TASKS_MODULE + ".get_completed_programs")
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME="test-service-username")
|
||||
class AwardProgramCertificatesTestCase(
|
||||
CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase
|
||||
):
|
||||
@mock.patch(TASKS_MODULE + '.award_program_certificate')
|
||||
@mock.patch(TASKS_MODULE + '.get_certified_programs')
|
||||
@mock.patch(TASKS_MODULE + '.get_completed_programs')
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME='test-service-username')
|
||||
class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
Tests for the 'award_program_certificates' celery task.
|
||||
"""
|
||||
@@ -159,11 +134,11 @@ class AwardProgramCertificatesTestCase(
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.create_credentials_config()
|
||||
self.student = UserFactory.create(username="test-student")
|
||||
self.student = UserFactory.create(username='test-student')
|
||||
self.site = SiteFactory()
|
||||
self.site_configuration = SiteConfigurationFactory(site=self.site)
|
||||
self.catalog_integration = self.create_catalog_integration()
|
||||
ApplicationFactory.create(name="credentials")
|
||||
ApplicationFactory.create(name='credentials')
|
||||
UserFactory.create(username=settings.CREDENTIALS_SERVICE_USERNAME)
|
||||
|
||||
def test_completion_check(
|
||||
@@ -202,26 +177,20 @@ class AwardProgramCertificatesTestCase(
|
||||
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
|
||||
actual_program_uuids = [
|
||||
call[0][2] for call in mock_award_program_certificate.call_args_list
|
||||
]
|
||||
actual_program_uuids = [call[0][2] for call in mock_award_program_certificate.call_args_list]
|
||||
assert actual_program_uuids == expected_awarded_program_uuids
|
||||
|
||||
actual_visible_dates = [
|
||||
call[0][3] for call in mock_award_program_certificate.call_args_list
|
||||
]
|
||||
actual_visible_dates = [call[0][3] for call in mock_award_program_certificate.call_args_list]
|
||||
assert actual_visible_dates == expected_awarded_program_uuids
|
||||
# program uuids are same as mock dates
|
||||
|
||||
@mock.patch(
|
||||
"openedx.core.djangoapps.site_configuration.helpers.get_current_site_configuration"
|
||||
)
|
||||
@mock.patch('openedx.core.djangoapps.site_configuration.helpers.get_current_site_configuration')
|
||||
def test_awarding_certs_with_skip_program_certificate(
|
||||
self,
|
||||
mocked_get_current_site_configuration,
|
||||
mock_get_completed_programs,
|
||||
mock_get_certified_programs,
|
||||
mock_award_program_certificate,
|
||||
self,
|
||||
mocked_get_current_site_configuration,
|
||||
mock_get_completed_programs,
|
||||
mock_get_certified_programs,
|
||||
mock_award_program_certificate,
|
||||
):
|
||||
"""
|
||||
Checks that the Credentials API is used to award certificates for
|
||||
@@ -235,7 +204,9 @@ class AwardProgramCertificatesTestCase(
|
||||
mock_get_certified_programs.return_value = [1]
|
||||
|
||||
# programs to be skipped
|
||||
self.site_configuration.site_values = {"programs_without_certificates": [2]}
|
||||
self.site_configuration.site_values = {
|
||||
"programs_without_certificates": [2]
|
||||
}
|
||||
self.site_configuration.save()
|
||||
mocked_get_current_site_configuration.return_value = self.site_configuration
|
||||
|
||||
@@ -244,31 +215,28 @@ class AwardProgramCertificatesTestCase(
|
||||
expected_awarded_program_uuids = [3, 4]
|
||||
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
actual_program_uuids = [
|
||||
call[0][2] for call in mock_award_program_certificate.call_args_list
|
||||
]
|
||||
actual_program_uuids = [call[0][2] for call in mock_award_program_certificate.call_args_list]
|
||||
assert actual_program_uuids == expected_awarded_program_uuids
|
||||
actual_visible_dates = [
|
||||
call[0][3] for call in mock_award_program_certificate.call_args_list
|
||||
]
|
||||
actual_visible_dates = [call[0][3] for call in mock_award_program_certificate.call_args_list]
|
||||
assert actual_visible_dates == expected_awarded_program_uuids
|
||||
# program uuids are same as mock dates
|
||||
|
||||
@ddt.data(
|
||||
("credentials", "enable_learner_issuance"),
|
||||
('credentials', 'enable_learner_issuance'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_retry_if_config_disabled(
|
||||
self, disabled_config_type, disabled_config_attribute, *mock_helpers
|
||||
self,
|
||||
disabled_config_type,
|
||||
disabled_config_attribute,
|
||||
*mock_helpers
|
||||
):
|
||||
"""
|
||||
Checks that the task is aborted if any relevant api configs are
|
||||
disabled.
|
||||
"""
|
||||
getattr(self, f"create_{disabled_config_type}_config")(
|
||||
**{disabled_config_attribute: False}
|
||||
)
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.warning") as mock_warning:
|
||||
getattr(self, f'create_{disabled_config_type}_config')(**{disabled_config_attribute: False})
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.warning') as mock_warning:
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
assert mock_warning.called
|
||||
@@ -280,8 +248,8 @@ class AwardProgramCertificatesTestCase(
|
||||
Checks that the task will be aborted and not retried if the username
|
||||
passed was not found, and that an exception is logged.
|
||||
"""
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.exception") as mock_exception:
|
||||
tasks.award_program_certificates.delay("nonexistent-username").get()
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_exception:
|
||||
tasks.award_program_certificates.delay('nonexistent-username').get()
|
||||
assert mock_exception.called
|
||||
for mock_helper in mock_helpers:
|
||||
assert not mock_helper.called
|
||||
@@ -302,13 +270,13 @@ class AwardProgramCertificatesTestCase(
|
||||
assert not mock_get_certified_programs.called
|
||||
assert not mock_award_program_certificate.called
|
||||
|
||||
@mock.patch("openedx.core.djangoapps.site_configuration.helpers.get_value")
|
||||
@mock.patch('openedx.core.djangoapps.site_configuration.helpers.get_value')
|
||||
def test_programs_without_certificates(
|
||||
self,
|
||||
mock_get_value,
|
||||
mock_get_completed_programs,
|
||||
mock_get_certified_programs,
|
||||
mock_award_program_certificate,
|
||||
mock_award_program_certificate
|
||||
):
|
||||
"""
|
||||
Checks that the task will be aborted without further action if there exists a list
|
||||
@@ -321,22 +289,22 @@ class AwardProgramCertificatesTestCase(
|
||||
assert not mock_get_certified_programs.called
|
||||
assert not mock_award_program_certificate.called
|
||||
|
||||
@mock.patch(TASKS_MODULE + ".get_credentials_api_client")
|
||||
@mock.patch(TASKS_MODULE + '.get_credentials_api_client')
|
||||
def test_failure_to_create_api_client_retries(
|
||||
self,
|
||||
mock_get_api_client,
|
||||
mock_get_completed_programs,
|
||||
mock_get_certified_programs,
|
||||
mock_award_program_certificate,
|
||||
mock_award_program_certificate
|
||||
):
|
||||
"""
|
||||
Checks that we log an exception and retry if the API client isn't creating.
|
||||
"""
|
||||
mock_get_api_client.side_effect = Exception("boom")
|
||||
mock_get_api_client.side_effect = Exception('boom')
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_get_certified_programs.return_value = [2]
|
||||
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.exception") as mock_exception:
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_exception:
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
|
||||
@@ -379,30 +347,25 @@ class AwardProgramCertificatesTestCase(
|
||||
"""
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_get_certified_programs.side_effect = [[], [2]]
|
||||
mock_award_program_certificate.side_effect = self._make_side_effect(
|
||||
[Exception("boom"), None]
|
||||
)
|
||||
mock_award_program_certificate.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.info") as mock_info, mock.patch(
|
||||
TASKS_MODULE + ".LOGGER.exception"
|
||||
) as mock_warning:
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.info') as mock_info, \
|
||||
mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_warning:
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
|
||||
assert mock_award_program_certificate.call_count == 3
|
||||
mock_warning.assert_called_once_with(
|
||||
"Failed to award certificate for program {uuid} to user {username}.".format(
|
||||
uuid=1, username=self.student.username
|
||||
)
|
||||
)
|
||||
mock_info.assert_any_call(
|
||||
f"Awarded certificate for program {1} to user {self.student.username}"
|
||||
)
|
||||
mock_info.assert_any_call(
|
||||
f"Awarded certificate for program {2} to user {self.student.username}"
|
||||
'Failed to award certificate for program {uuid} to user {username}.'.format(
|
||||
uuid=1,
|
||||
username=self.student.username)
|
||||
)
|
||||
mock_info.assert_any_call(f"Awarded certificate for program {1} to user {self.student.username}")
|
||||
mock_info.assert_any_call(f"Awarded certificate for program {2} to user {self.student.username}")
|
||||
|
||||
def test_retry_on_programs_api_errors(
|
||||
self, mock_get_completed_programs, *_mock_helpers
|
||||
self,
|
||||
mock_get_completed_programs,
|
||||
*_mock_helpers
|
||||
):
|
||||
"""
|
||||
Ensures that any otherwise-unhandled errors that arise while trying
|
||||
@@ -410,9 +373,7 @@ class AwardProgramCertificatesTestCase(
|
||||
transient API errors) will cause the task to be failed and queued for
|
||||
retry.
|
||||
"""
|
||||
mock_get_completed_programs.side_effect = self._make_side_effect(
|
||||
[Exception("boom"), None]
|
||||
)
|
||||
mock_get_completed_programs.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
assert mock_get_completed_programs.call_count == 3
|
||||
|
||||
@@ -430,9 +391,7 @@ class AwardProgramCertificatesTestCase(
|
||||
"""
|
||||
mock_get_completed_programs.return_value = {1: 1, 2: 2}
|
||||
mock_get_certified_programs.return_value = [1]
|
||||
mock_get_certified_programs.side_effect = self._make_side_effect(
|
||||
[Exception("boom"), None]
|
||||
)
|
||||
mock_get_certified_programs.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
tasks.award_program_certificates.delay(self.student.username).get()
|
||||
assert mock_get_certified_programs.call_count == 2
|
||||
assert mock_award_program_certificate.call_count == 1
|
||||
@@ -505,68 +464,59 @@ class PostCourseCertificateTestCase(TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self): # lint-amnesty, pylint: disable=super-method-not-called
|
||||
self.student = UserFactory.create(username="test-student")
|
||||
self.student = UserFactory.create(username='test-student')
|
||||
self.course = CourseOverviewFactory.create(
|
||||
self_paced=True # Any option to allow the certificate to be viewable for the course
|
||||
)
|
||||
self.certificate = GeneratedCertificateFactory(
|
||||
user=self.student,
|
||||
mode="verified",
|
||||
mode='verified',
|
||||
course_id=self.course.id,
|
||||
status="downloadable",
|
||||
status='downloadable'
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
@mock.patch("openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url")
|
||||
@mock.patch('openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url')
|
||||
def test_post_course_certificate(self, mock_get_api_base_url):
|
||||
"""
|
||||
Ensure the correct API call gets made
|
||||
"""
|
||||
mock_get_api_base_url.return_value = "http://test-server/"
|
||||
mock_get_api_base_url.return_value = 'http://test-server/'
|
||||
test_client = requests.Session()
|
||||
test_client.auth = SuppliedJwtAuth("test-token")
|
||||
test_client.auth = SuppliedJwtAuth('test-token')
|
||||
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
"http://test-server/credentials/",
|
||||
'http://test-server/credentials/',
|
||||
)
|
||||
|
||||
visible_date = datetime.now()
|
||||
|
||||
tasks.post_course_certificate(
|
||||
test_client, self.student.username, self.certificate, visible_date
|
||||
)
|
||||
tasks.post_course_certificate(test_client, self.student.username, self.certificate, visible_date)
|
||||
|
||||
expected_body = {
|
||||
"username": self.student.username,
|
||||
"status": "awarded",
|
||||
"credential": {
|
||||
"course_run_key": str(self.certificate.course_id),
|
||||
"mode": self.certificate.mode,
|
||||
"type": tasks.COURSE_CERTIFICATE,
|
||||
'username': self.student.username,
|
||||
'status': 'awarded',
|
||||
'credential': {
|
||||
'course_run_key': str(self.certificate.course_id),
|
||||
'mode': self.certificate.mode,
|
||||
'type': tasks.COURSE_CERTIFICATE,
|
||||
},
|
||||
"date_override": None,
|
||||
"attributes": [
|
||||
{
|
||||
"name": "visible_date",
|
||||
"value": visible_date.strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
), # text representation of date
|
||||
}
|
||||
],
|
||||
'date_override': None,
|
||||
'attributes': [{
|
||||
'name': 'visible_date',
|
||||
'value': visible_date.strftime('%Y-%m-%dT%H:%M:%SZ') # text representation of date
|
||||
}]
|
||||
}
|
||||
last_request_body = httpretty.last_request().body.decode("utf-8")
|
||||
last_request_body = httpretty.last_request().body.decode('utf-8')
|
||||
assert json.loads(last_request_body) == expected_body
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@ddt.ddt
|
||||
@mock.patch(
|
||||
"lms.djangoapps.certificates.api.auto_certificate_generation_enabled",
|
||||
mock.Mock(return_value=True),
|
||||
)
|
||||
@mock.patch(TASKS_MODULE + ".post_course_certificate")
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME="test-service-username")
|
||||
@mock.patch("lms.djangoapps.certificates.api.auto_certificate_generation_enabled", mock.Mock(return_value=True))
|
||||
@mock.patch(TASKS_MODULE + '.post_course_certificate')
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME='test-service-username')
|
||||
class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
Test the award_course_certificate celery task
|
||||
@@ -579,21 +529,21 @@ class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase):
|
||||
self.course = CourseOverviewFactory.create(
|
||||
self_paced=True, # Any option to allow the certificate to be viewable for the course
|
||||
certificate_available_date=self.available_date,
|
||||
certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE,
|
||||
certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
|
||||
)
|
||||
self.student = UserFactory.create(username="test-student")
|
||||
self.student = UserFactory.create(username='test-student')
|
||||
# Instantiate the Certificate first so that the config doesn't execute issuance
|
||||
self.certificate = GeneratedCertificateFactory.create(
|
||||
user=self.student,
|
||||
mode="verified",
|
||||
mode='verified',
|
||||
course_id=self.course.id,
|
||||
status="downloadable",
|
||||
status='downloadable'
|
||||
)
|
||||
|
||||
self.create_credentials_config()
|
||||
self.site = SiteFactory()
|
||||
|
||||
ApplicationFactory.create(name="credentials")
|
||||
ApplicationFactory.create(name='credentials')
|
||||
UserFactory.create(username=settings.CREDENTIALS_SERVICE_USERNAME)
|
||||
|
||||
def _add_certificate_date_override(self):
|
||||
@@ -602,12 +552,12 @@ class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
self.certificate.date_override = CertificateDateOverrideFactory.create(
|
||||
generated_certificate=self.certificate,
|
||||
overridden_by=UserFactory.create(username="test-admin"),
|
||||
overridden_by=UserFactory.create(username='test-admin'),
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
"verified",
|
||||
"no-id-professional",
|
||||
'verified',
|
||||
'no-id-professional',
|
||||
)
|
||||
def test_award_course_certificates(self, mode, mock_post_course_certificate):
|
||||
"""
|
||||
@@ -615,119 +565,89 @@ class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
self.certificate.mode = mode
|
||||
self.certificate.save()
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.course.id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.course.id)).get()
|
||||
call_args, _ = mock_post_course_certificate.call_args
|
||||
assert call_args[1] == self.student.username
|
||||
assert call_args[2] == self.certificate
|
||||
assert call_args[3] == self.certificate.modified_date
|
||||
|
||||
def test_award_course_certificates_available_date(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_certificates_available_date(self, mock_post_course_certificate):
|
||||
"""
|
||||
Tests the API POST method is called with available date when the course is not self paced
|
||||
"""
|
||||
self.course.self_paced = False
|
||||
self.course.save()
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.course.id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.course.id)).get()
|
||||
call_args, _ = mock_post_course_certificate.call_args
|
||||
assert call_args[1] == self.student.username
|
||||
assert call_args[2] == self.certificate
|
||||
assert call_args[3] == self.available_date
|
||||
|
||||
def test_award_course_certificates_override_date(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_certificates_override_date(self, mock_post_course_certificate):
|
||||
"""
|
||||
Tests the API POST method is called with date override when present
|
||||
"""
|
||||
self._add_certificate_date_override()
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.course.id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.course.id)).get()
|
||||
call_args, _ = mock_post_course_certificate.call_args
|
||||
assert call_args[1] == self.student.username
|
||||
assert call_args[2] == self.certificate
|
||||
assert call_args[3] == self.certificate.modified_date
|
||||
assert call_args[4] == self.certificate.date_override.date
|
||||
|
||||
def test_award_course_cert_not_called_if_disabled(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_cert_not_called_if_disabled(self, mock_post_course_certificate):
|
||||
"""
|
||||
Test that the post method is never called if the config is disabled
|
||||
"""
|
||||
self.create_credentials_config(enabled=False)
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.warning") as mock_warning:
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.warning') as mock_warning:
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.course.id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.course.id)).get()
|
||||
assert mock_warning.called
|
||||
assert not mock_post_course_certificate.called
|
||||
|
||||
def test_award_course_cert_not_called_if_user_not_found(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_cert_not_called_if_user_not_found(self, mock_post_course_certificate):
|
||||
"""
|
||||
Test that the post method is never called if the user isn't found by username
|
||||
"""
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.exception") as mock_exception:
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_exception:
|
||||
# Use a random username here since this user won't be found in the DB
|
||||
tasks.award_course_certificate.delay(
|
||||
"random_username", str(self.course.id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay('random_username', str(self.course.id)).get()
|
||||
assert mock_exception.called
|
||||
assert not mock_post_course_certificate.called
|
||||
|
||||
def test_award_course_cert_not_called_if_certificate_not_found(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_cert_not_called_if_certificate_not_found(self, mock_post_course_certificate):
|
||||
"""
|
||||
Test that the post method is never called if the certificate doesn't exist for the user and course
|
||||
"""
|
||||
self.certificate.delete()
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.exception") as mock_exception:
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.course.id)
|
||||
).get()
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_exception:
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.course.id)).get()
|
||||
assert mock_exception.called
|
||||
assert not mock_post_course_certificate.called
|
||||
|
||||
def test_award_course_cert_not_called_if_course_overview_not_found(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_cert_not_called_if_course_overview_not_found(self, mock_post_course_certificate):
|
||||
"""
|
||||
Test that the post method is never called if the CourseOverview isn't found
|
||||
"""
|
||||
self.course.delete()
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.exception") as mock_exception:
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_exception:
|
||||
# Use the certificate course id here since the course will be deleted
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.certificate.course_id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.certificate.course_id)).get()
|
||||
assert mock_exception.called
|
||||
assert not mock_post_course_certificate.called
|
||||
|
||||
def test_award_course_cert_not_called_if_certificated_not_verified_mode(
|
||||
self, mock_post_course_certificate
|
||||
):
|
||||
def test_award_course_cert_not_called_if_certificated_not_verified_mode(self, mock_post_course_certificate):
|
||||
"""
|
||||
Test that the post method is never called if the GeneratedCertificate is an 'audit' cert
|
||||
"""
|
||||
# Temporarily disable the config so the signal isn't handled from .save
|
||||
self.create_credentials_config(enabled=False)
|
||||
self.certificate.mode = "audit"
|
||||
self.certificate.mode = 'audit'
|
||||
self.certificate.save()
|
||||
self.create_credentials_config()
|
||||
|
||||
tasks.award_course_certificate.delay(
|
||||
self.student.username, str(self.certificate.course_id)
|
||||
).get()
|
||||
tasks.award_course_certificate.delay(self.student.username, str(self.certificate.course_id)).get()
|
||||
assert not mock_post_course_certificate.called
|
||||
|
||||
|
||||
@@ -738,44 +658,42 @@ class RevokeProgramCertificateTestCase(TestCase):
|
||||
"""
|
||||
|
||||
@httpretty.activate
|
||||
@mock.patch("openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url")
|
||||
@mock.patch('openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url')
|
||||
def test_revoke_program_certificate(self, mock_get_api_base_url):
|
||||
"""
|
||||
Ensure the correct API call gets made
|
||||
"""
|
||||
mock_get_api_base_url.return_value = "http://test-server/"
|
||||
test_username = "test-username"
|
||||
mock_get_api_base_url.return_value = 'http://test-server/'
|
||||
test_username = 'test-username'
|
||||
test_client = requests.Session()
|
||||
test_client.auth = SuppliedJwtAuth("test-token")
|
||||
test_client.auth = SuppliedJwtAuth('test-token')
|
||||
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
"http://test-server/credentials/",
|
||||
'http://test-server/credentials/',
|
||||
)
|
||||
|
||||
tasks.revoke_program_certificate(test_client, test_username, 123)
|
||||
|
||||
expected_body = {
|
||||
"username": test_username,
|
||||
"status": "revoked",
|
||||
"credential": {
|
||||
"program_uuid": 123,
|
||||
"type": tasks.PROGRAM_CERTIFICATE,
|
||||
},
|
||||
'username': test_username,
|
||||
'status': 'revoked',
|
||||
'credential': {
|
||||
'program_uuid': 123,
|
||||
'type': tasks.PROGRAM_CERTIFICATE,
|
||||
}
|
||||
}
|
||||
last_request_body = httpretty.last_request().body.decode("utf-8")
|
||||
last_request_body = httpretty.last_request().body.decode('utf-8')
|
||||
assert json.loads(last_request_body) == expected_body
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@ddt.ddt
|
||||
@mock.patch(TASKS_MODULE + ".revoke_program_certificate")
|
||||
@mock.patch(TASKS_MODULE + ".get_certified_programs")
|
||||
@mock.patch(TASKS_MODULE + ".get_inverted_programs")
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME="test-service-username")
|
||||
class RevokeProgramCertificatesTestCase(
|
||||
CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase
|
||||
):
|
||||
@mock.patch(TASKS_MODULE + '.revoke_program_certificate')
|
||||
@mock.patch(TASKS_MODULE + '.get_certified_programs')
|
||||
@mock.patch(TASKS_MODULE + '.get_inverted_programs')
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME='test-service-username')
|
||||
class RevokeProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
Tests for the 'revoke_program_certificates' celery task.
|
||||
"""
|
||||
@@ -783,15 +701,15 @@ class RevokeProgramCertificatesTestCase(
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.student = UserFactory.create(username="test-student")
|
||||
self.course_key = "course-v1:testX+test101+2T2020"
|
||||
self.student = UserFactory.create(username='test-student')
|
||||
self.course_key = 'course-v1:testX+test101+2T2020'
|
||||
self.site = SiteFactory()
|
||||
self.site_configuration = SiteConfigurationFactory(site=self.site)
|
||||
ApplicationFactory.create(name="credentials")
|
||||
ApplicationFactory.create(name='credentials')
|
||||
UserFactory.create(username=settings.CREDENTIALS_SERVICE_USERNAME)
|
||||
self.create_credentials_config()
|
||||
|
||||
self.inverted_programs = {self.course_key: [{"uuid": 1}, {"uuid": 2}]}
|
||||
self.inverted_programs = {self.course_key: [{'uuid': 1}, {'uuid': 2}]}
|
||||
|
||||
def _make_side_effect(self, side_effects):
|
||||
"""
|
||||
@@ -824,9 +742,7 @@ class RevokeProgramCertificatesTestCase(
|
||||
Checks that the Programs API is used correctly to determine completed
|
||||
programs.
|
||||
"""
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
mock_get_inverted_programs.assert_any_call(self.student)
|
||||
|
||||
def test_revoke_program_certificate(
|
||||
@@ -841,37 +757,34 @@ class RevokeProgramCertificatesTestCase(
|
||||
"""
|
||||
expected_program_uuid = 1
|
||||
mock_get_inverted_programs.return_value = {
|
||||
self.course_key: [{"uuid": expected_program_uuid}]
|
||||
self.course_key: [{'uuid': expected_program_uuid}]
|
||||
}
|
||||
mock_get_certified_programs.return_value = [expected_program_uuid]
|
||||
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
|
||||
call_args, _ = mock_revoke_program_certificate.call_args
|
||||
assert call_args[1] == self.student.username
|
||||
assert call_args[2] == expected_program_uuid
|
||||
|
||||
@ddt.data(
|
||||
("credentials", "enable_learner_issuance"),
|
||||
('credentials', 'enable_learner_issuance'),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_retry_if_config_disabled(
|
||||
self, disabled_config_type, disabled_config_attribute, *mock_helpers
|
||||
self,
|
||||
disabled_config_type,
|
||||
disabled_config_attribute,
|
||||
*mock_helpers
|
||||
):
|
||||
"""
|
||||
Checks that the task is aborted if any relevant api configs are
|
||||
disabled.
|
||||
"""
|
||||
getattr(self, f"create_{disabled_config_type}_config")(
|
||||
**{disabled_config_attribute: False}
|
||||
)
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.warning") as mock_warning:
|
||||
getattr(self, f'create_{disabled_config_type}_config')(**{disabled_config_attribute: False})
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.warning') as mock_warning:
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
assert mock_warning.called
|
||||
for mock_helper in mock_helpers:
|
||||
assert not mock_helper.called
|
||||
@@ -881,10 +794,8 @@ class RevokeProgramCertificatesTestCase(
|
||||
Checks that the task will be aborted and not retried if the username
|
||||
passed was not found, and that an exception is logged.
|
||||
"""
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.exception") as mock_exception:
|
||||
tasks.revoke_program_certificates.delay(
|
||||
"nonexistent-username", self.course_key
|
||||
).get()
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.exception') as mock_exception:
|
||||
tasks.revoke_program_certificates.delay('nonexistent-username', self.course_key).get()
|
||||
assert mock_exception.called
|
||||
for mock_helper in mock_helpers:
|
||||
assert not mock_helper.called
|
||||
@@ -900,9 +811,7 @@ class RevokeProgramCertificatesTestCase(
|
||||
not part of any program.
|
||||
"""
|
||||
mock_get_inverted_programs.return_value = {}
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
assert mock_get_inverted_programs.called
|
||||
assert not mock_get_certified_programs.called
|
||||
assert not mock_revoke_program_certificate.called
|
||||
@@ -921,29 +830,20 @@ class RevokeProgramCertificatesTestCase(
|
||||
"""
|
||||
mock_get_inverted_programs.return_value = self.inverted_programs
|
||||
mock_get_certified_programs.side_effect = [[1], [1, 2]]
|
||||
mock_revoke_program_certificate.side_effect = self._make_side_effect(
|
||||
[Exception("boom"), None]
|
||||
)
|
||||
mock_revoke_program_certificate.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
|
||||
with mock.patch(TASKS_MODULE + ".LOGGER.info") as mock_info, mock.patch(
|
||||
TASKS_MODULE + ".LOGGER.warning"
|
||||
) as mock_warning:
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
with mock.patch(TASKS_MODULE + '.LOGGER.info') as mock_info, \
|
||||
mock.patch(TASKS_MODULE + '.LOGGER.warning') as mock_warning:
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
|
||||
assert mock_revoke_program_certificate.call_count == 3
|
||||
mock_warning.assert_called_once_with(
|
||||
"Failed to revoke certificate for program {uuid} of user {username}.".format(
|
||||
uuid=1, username=self.student.username
|
||||
)
|
||||
)
|
||||
mock_info.assert_any_call(
|
||||
f"Revoked certificate for program {1} for user {self.student.username}"
|
||||
)
|
||||
mock_info.assert_any_call(
|
||||
f"Revoked certificate for program {2} for user {self.student.username}"
|
||||
'Failed to revoke certificate for program {uuid} of user {username}.'.format(
|
||||
uuid=1,
|
||||
username=self.student.username)
|
||||
)
|
||||
mock_info.assert_any_call(f"Revoked certificate for program {1} for user {self.student.username}")
|
||||
mock_info.assert_any_call(f"Revoked certificate for program {2} for user {self.student.username}")
|
||||
|
||||
def test_retry_on_credentials_api_errors(
|
||||
self,
|
||||
@@ -959,12 +859,8 @@ class RevokeProgramCertificatesTestCase(
|
||||
"""
|
||||
mock_get_inverted_programs.return_value = self.inverted_programs
|
||||
mock_get_certified_programs.return_value = [1]
|
||||
mock_get_certified_programs.side_effect = self._make_side_effect(
|
||||
[Exception("boom"), None]
|
||||
)
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
mock_get_certified_programs.side_effect = self._make_side_effect([Exception('boom'), None])
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
assert mock_get_certified_programs.call_count == 2
|
||||
assert mock_revoke_program_certificate.call_count == 1
|
||||
|
||||
@@ -985,9 +881,7 @@ class RevokeProgramCertificatesTestCase(
|
||||
[exception, None]
|
||||
)
|
||||
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
|
||||
assert mock_revoke_program_certificate.call_count == 3
|
||||
|
||||
@@ -1008,9 +902,7 @@ class RevokeProgramCertificatesTestCase(
|
||||
[exception, None]
|
||||
)
|
||||
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
|
||||
assert mock_revoke_program_certificate.call_count == 2
|
||||
|
||||
@@ -1031,9 +923,7 @@ class RevokeProgramCertificatesTestCase(
|
||||
[exception, None]
|
||||
)
|
||||
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
|
||||
assert mock_revoke_program_certificate.call_count == 2
|
||||
|
||||
@@ -1052,52 +942,47 @@ class RevokeProgramCertificatesTestCase(
|
||||
with mock.patch(
|
||||
TASKS_MODULE + ".get_credentials_api_client"
|
||||
) as mock_get_api_client, mock.patch(
|
||||
TASKS_MODULE + ".LOGGER.exception"
|
||||
TASKS_MODULE + '.LOGGER.exception'
|
||||
) as mock_exception:
|
||||
mock_get_api_client.side_effect = Exception("boom")
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.revoke_program_certificates.delay(
|
||||
self.student.username, self.course_key
|
||||
).get()
|
||||
tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get()
|
||||
assert mock_exception.called
|
||||
assert mock_get_api_client.call_count == (tasks.MAX_RETRIES + 1)
|
||||
assert not mock_revoke_program_certificate.called
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME="test-service-username")
|
||||
@override_settings(CREDENTIALS_SERVICE_USERNAME='test-service-username')
|
||||
class UpdateCredentialsCourseCertificateConfigurationAvailableDateTestCase(TestCase):
|
||||
"""
|
||||
Tests for the update_credentials_course_certificate_configuration_available_date function
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.course = CourseOverviewFactory.create(
|
||||
certificate_available_date=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
certificate_available_date=datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
)
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug="verified")
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug="audit")
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug='verified')
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug='audit')
|
||||
self.available_date = self.course.certificate_available_date
|
||||
self.course_id = self.course.id
|
||||
self.credentials_worker = UserFactory(username="test-service-username")
|
||||
self.credentials_worker = UserFactory(username='test-service-username')
|
||||
|
||||
def test_update_course_cert_available_date(self):
|
||||
with mock.patch(
|
||||
TASKS_MODULE + ".post_course_certificate_configuration"
|
||||
) as update_posted:
|
||||
with mock.patch(TASKS_MODULE + '.post_course_certificate_configuration') as update_posted:
|
||||
tasks.update_credentials_course_certificate_configuration_available_date(
|
||||
self.course_id, self.available_date
|
||||
self.course_id,
|
||||
self.available_date
|
||||
)
|
||||
update_posted.assert_called_once()
|
||||
|
||||
def test_course_with_two_paid_modes(self):
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug="professional")
|
||||
with mock.patch(
|
||||
TASKS_MODULE + ".post_course_certificate_configuration"
|
||||
) as update_posted:
|
||||
CourseModeFactory.create(course_id=self.course.id, mode_slug='professional')
|
||||
with mock.patch(TASKS_MODULE + '.post_course_certificate_configuration') as update_posted:
|
||||
tasks.update_credentials_course_certificate_configuration_available_date(
|
||||
self.course_id, self.available_date
|
||||
self.course_id,
|
||||
self.available_date
|
||||
)
|
||||
update_posted.assert_not_called()
|
||||
|
||||
@@ -1107,80 +992,74 @@ class PostCourseCertificateConfigurationTestCase(TestCase):
|
||||
"""
|
||||
Test the post_course_certificate_configuration function
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.certificate = {
|
||||
"mode": "verified",
|
||||
"course_id": "testCourse",
|
||||
'mode': 'verified',
|
||||
'course_id': 'testCourse',
|
||||
}
|
||||
|
||||
@httpretty.activate
|
||||
@mock.patch("openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url")
|
||||
@mock.patch('openedx.core.djangoapps.programs.tasks.get_credentials_api_base_url')
|
||||
def test_post_course_certificate_configuration(self, mock_get_api_base_url):
|
||||
"""
|
||||
Ensure the correct API call gets made
|
||||
"""
|
||||
mock_get_api_base_url.return_value = "http://test-server/"
|
||||
mock_get_api_base_url.return_value = 'http://test-server/'
|
||||
test_client = requests.Session()
|
||||
test_client.auth = SuppliedJwtAuth("test-token")
|
||||
test_client.auth = SuppliedJwtAuth('test-token')
|
||||
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
"http://test-server/course_certificates/",
|
||||
'http://test-server/course_certificates/',
|
||||
)
|
||||
|
||||
available_date = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
available_date = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
tasks.post_course_certificate_configuration(
|
||||
test_client, self.certificate, available_date
|
||||
)
|
||||
tasks.post_course_certificate_configuration(test_client, self.certificate, available_date)
|
||||
|
||||
expected_body = {
|
||||
"course_id": "testCourse",
|
||||
"certificate_type": "verified",
|
||||
"course_id": 'testCourse',
|
||||
"certificate_type": 'verified',
|
||||
"certificate_available_date": available_date,
|
||||
"is_active": True,
|
||||
"is_active": True
|
||||
}
|
||||
last_request_body = httpretty.last_request().body.decode("utf-8")
|
||||
last_request_body = httpretty.last_request().body.decode('utf-8')
|
||||
assert json.loads(last_request_body) == expected_body
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class UpdateCertificateVisibleDatesOnCourseUpdateTestCase(
|
||||
CredentialsApiConfigMixin, TestCase
|
||||
):
|
||||
class UpdateCertificateVisibleDatesOnCourseUpdateTestCase(CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
Tests for the `update_certificate_visible_date_on_course_update` task.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.credentials_api_config = self.create_credentials_config(enabled=False)
|
||||
# setup course
|
||||
self.course = CourseOverviewFactory.create()
|
||||
# setup users
|
||||
self.student1 = UserFactory.create(username="test-student1")
|
||||
self.student2 = UserFactory.create(username="test-student2")
|
||||
self.student3 = UserFactory.create(username="test-student3")
|
||||
self.student1 = UserFactory.create(username='test-student1')
|
||||
self.student2 = UserFactory.create(username='test-student2')
|
||||
self.student3 = UserFactory.create(username='test-student3')
|
||||
# award certificates to users in course we created
|
||||
self.certificate_student1 = GeneratedCertificateFactory.create(
|
||||
user=self.student1,
|
||||
mode="verified",
|
||||
mode='verified',
|
||||
course_id=self.course.id,
|
||||
status="downloadable",
|
||||
status='downloadable'
|
||||
)
|
||||
self.certificate_student2 = GeneratedCertificateFactory.create(
|
||||
user=self.student2,
|
||||
mode="verified",
|
||||
mode='verified',
|
||||
course_id=self.course.id,
|
||||
status="downloadable",
|
||||
status='downloadable'
|
||||
)
|
||||
self.certificate_student3 = GeneratedCertificateFactory.create(
|
||||
user=self.student3,
|
||||
mode="verified",
|
||||
mode='verified',
|
||||
course_id=self.course.id,
|
||||
status="downloadable",
|
||||
status='downloadable'
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
@@ -1196,9 +1075,7 @@ class UpdateCertificateVisibleDatesOnCourseUpdateTestCase(
|
||||
exception when the max number of retries has reached.
|
||||
"""
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.update_certificate_visible_date_on_course_update(
|
||||
self.course.id
|
||||
) # pylint: disable=no-value-for-parameter
|
||||
tasks.update_certificate_visible_date_on_course_update(self.course.id) # pylint: disable=no-value-for-parameter
|
||||
|
||||
def test_update_visible_dates(self):
|
||||
"""
|
||||
@@ -1212,24 +1089,17 @@ class UpdateCertificateVisibleDatesOnCourseUpdateTestCase(
|
||||
self.credentials_api_config.enabled = True
|
||||
self.credentials_api_config.enable_learner_issuance = True
|
||||
|
||||
with mock.patch(
|
||||
f"{TASKS_MODULE}.award_course_certificate.delay"
|
||||
) as award_course_cert:
|
||||
tasks.update_certificate_visible_date_on_course_update(
|
||||
self.course.id
|
||||
) # pylint: disable=no-value-for-parameter
|
||||
with mock.patch(f"{TASKS_MODULE}.award_course_certificate.delay") as award_course_cert:
|
||||
tasks.update_certificate_visible_date_on_course_update(self.course.id) # pylint: disable=no-value-for-parameter
|
||||
|
||||
assert award_course_cert.call_count == 3
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class UpdateCertificateAvailableDateOnCourseUpdateTestCase(
|
||||
CredentialsApiConfigMixin, TestCase
|
||||
):
|
||||
class UpdateCertificateAvailableDateOnCourseUpdateTestCase(CredentialsApiConfigMixin, TestCase):
|
||||
"""
|
||||
Tests for the `update_certificate_available_date_on_course_update` task.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.credentials_api_config = self.create_credentials_config(enabled=False)
|
||||
@@ -1249,9 +1119,7 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(
|
||||
course = CourseOverviewFactory.create()
|
||||
|
||||
with pytest.raises(MaxRetriesExceededError):
|
||||
tasks.update_certificate_available_date_on_course_update(
|
||||
course.id
|
||||
) # pylint: disable=no-value-for-parameter
|
||||
tasks.update_certificate_available_date_on_course_update(course.id) # pylint: disable=no-value-for-parameter
|
||||
|
||||
def test_update_certificate_available_date_with_self_paced_course(self):
|
||||
"""
|
||||
@@ -1265,19 +1133,16 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(
|
||||
self.credentials_api_config.enable_learner_issuance = True
|
||||
|
||||
course = CourseOverviewFactory.create(
|
||||
self_paced=True, certificate_available_date=None
|
||||
self_paced=True,
|
||||
certificate_available_date=None
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
f"{TASKS_MODULE}.update_credentials_course_certificate_configuration_available_date.delay"
|
||||
) as update_credentials_course_cert_config:
|
||||
tasks.update_certificate_available_date_on_course_update(
|
||||
course.id
|
||||
) # pylint: disable=no-value-for-parameter
|
||||
tasks.update_certificate_available_date_on_course_update(course.id) # pylint: disable=no-value-for-parameter
|
||||
|
||||
update_credentials_course_cert_config.assert_called_once_with(
|
||||
str(course.id), None
|
||||
)
|
||||
update_credentials_course_cert_config.assert_called_once_with(str(course.id), None)
|
||||
|
||||
def test_update_certificate_available_date_with_instructor_paced_course(self):
|
||||
"""
|
||||
@@ -1295,19 +1160,15 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(
|
||||
course = CourseOverviewFactory.create(
|
||||
self_paced=False,
|
||||
certificate_available_date=available_date,
|
||||
certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE,
|
||||
certificates_display_behavior=CertificatesDisplayBehaviors.END_WITH_DATE
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
f"{TASKS_MODULE}.update_credentials_course_certificate_configuration_available_date.delay"
|
||||
) as update_credentials_course_cert_config:
|
||||
tasks.update_certificate_available_date_on_course_update(
|
||||
course.id
|
||||
) # pylint: disable=no-value-for-parameter
|
||||
tasks.update_certificate_available_date_on_course_update(course.id) # pylint: disable=no-value-for-parameter
|
||||
|
||||
update_credentials_course_cert_config.assert_called_once_with(
|
||||
str(course.id), str(available_date)
|
||||
)
|
||||
update_credentials_course_cert_config.assert_called_once_with(str(course.id), str(available_date))
|
||||
|
||||
def test_update_certificate_available_date_with_expect_no_update(self):
|
||||
"""
|
||||
@@ -1322,7 +1183,7 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(
|
||||
course = CourseOverviewFactory.create(
|
||||
self_paced=False,
|
||||
certificate_available_date=available_date,
|
||||
certificates_display_behavior=CertificatesDisplayBehaviors.EARLY_NO_INFO,
|
||||
certificates_display_behavior=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
||||
)
|
||||
|
||||
expected_message = (
|
||||
@@ -1331,9 +1192,7 @@ class UpdateCertificateAvailableDateOnCourseUpdateTestCase(
|
||||
)
|
||||
|
||||
with LogCapture(level=logging.WARNING) as log_capture:
|
||||
tasks.update_certificate_available_date_on_course_update(
|
||||
course.id
|
||||
) # pylint: disable=no-value-for-parameter
|
||||
tasks.update_certificate_available_date_on_course_update(course.id) # pylint: disable=no-value-for-parameter
|
||||
|
||||
assert len(log_capture.records) == 1
|
||||
assert log_capture.records[0].getMessage() == expected_message
|
||||
|
||||
Reference in New Issue
Block a user