Commit 379d789c by Renzo Lucioni Committed by GitHub

Merge pull request #14405 from edx/renzo/fix-backpopulation

Fix program credential backpopulation command
parents f06be21f 4391a577
...@@ -7,11 +7,12 @@ from django.core.management import BaseCommand, CommandError ...@@ -7,11 +7,12 @@ from django.core.management import BaseCommand, CommandError
from django.db.models import Q from django.db.models import Q
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from provider.oauth2.models import Client from provider.oauth2.models import Client
import waffle
from certificates.models import GeneratedCertificate, CertificateStatuses # pylint: disable=import-error from certificates.models import GeneratedCertificate, CertificateStatuses # pylint: disable=import-error
from openedx.core.djangoapps.catalog.models import CatalogIntegration from openedx.core.djangoapps.catalog.models import CatalogIntegration
from openedx.core.djangoapps.programs.tasks.v1.tasks import award_program_certificates from openedx.core.djangoapps.programs.tasks.v1.tasks import award_program_certificates
from openedx.core.djangoapps.catalog.utils import get_programs from openedx.core.djangoapps.programs.utils import get_programs
# TODO: Log to console, even with debug mode disabled? # TODO: Log to console, even with debug mode disabled?
...@@ -85,7 +86,8 @@ class Command(BaseCommand): ...@@ -85,7 +86,8 @@ class Command(BaseCommand):
def _load_run_modes(self, user): def _load_run_modes(self, user):
"""Find all run modes which are part of a program.""" """Find all run modes which are part of a program."""
programs = get_programs(user) use_catalog = waffle.switch_is_active('get_programs_from_catalog')
programs = get_programs(user, use_catalog=use_catalog)
self.run_modes = self._flatten(programs) self.run_modes = self._flatten(programs)
def _flatten(self, programs): def _flatten(self, programs):
......
...@@ -21,7 +21,7 @@ COMMAND_MODULE = 'openedx.core.djangoapps.programs.management.commands.backpopul ...@@ -21,7 +21,7 @@ COMMAND_MODULE = 'openedx.core.djangoapps.programs.management.commands.backpopul
@ddt.ddt @ddt.ddt
@httpretty.activate @mock.patch(COMMAND_MODULE + '.get_programs')
@mock.patch(COMMAND_MODULE + '.award_program_certificates.delay') @mock.patch(COMMAND_MODULE + '.award_program_certificates.delay')
@skip_unless_lms @skip_unless_lms
class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase): class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase):
...@@ -42,17 +42,8 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -42,17 +42,8 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
self.catalog_integration = self.create_catalog_integration() self.catalog_integration = self.create_catalog_integration()
self.service_user = UserFactory(username=self.catalog_integration.service_username) self.service_user = UserFactory(username=self.catalog_integration.service_username)
def _mock_programs_api(self, data):
"""Helper for mocking out Catalog API URLs."""
self.assertTrue(httpretty.is_enabled(), msg='httpretty must be enabled to mock Catalog API calls.')
url = self.catalog_integration.internal_api_url.strip('/') + '/programs/'
body = json.dumps({'results': data})
httpretty.register_uri(httpretty.GET, url, body=body, content_type='application/json')
@ddt.data(True, False) @ddt.data(True, False)
def test_handle(self, commit, mock_task): def test_handle(self, commit, mock_task, mock_get_programs):
"""Verify that relevant tasks are only enqueued when the commit option is passed.""" """Verify that relevant tasks are only enqueued when the commit option is passed."""
data = [ data = [
factories.Program( factories.Program(
...@@ -64,7 +55,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -64,7 +55,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
] ]
), ),
] ]
self._mock_programs_api(data) mock_get_programs.return_value = data
GeneratedCertificateFactory( GeneratedCertificateFactory(
user=self.alice, user=self.alice,
...@@ -131,9 +122,9 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -131,9 +122,9 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
), ),
], ],
) )
def test_handle_flatten(self, data, mock_task): def test_handle_flatten(self, data, mock_task, mock_get_programs):
"""Verify that program structures are flattened correctly.""" """Verify that program structures are flattened correctly."""
self._mock_programs_api(data) mock_get_programs.return_value = data
GeneratedCertificateFactory( GeneratedCertificateFactory(
user=self.alice, user=self.alice,
...@@ -157,7 +148,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -157,7 +148,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
] ]
mock_task.assert_has_calls(calls, any_order=True) mock_task.assert_has_calls(calls, any_order=True)
def test_handle_username_dedup(self, mock_task): def test_handle_username_dedup(self, mock_task, mock_get_programs):
"""Verify that only one task is enqueued for a user with multiple eligible certs.""" """Verify that only one task is enqueued for a user with multiple eligible certs."""
data = [ data = [
factories.Program( factories.Program(
...@@ -170,7 +161,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -170,7 +161,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
] ]
), ),
] ]
self._mock_programs_api(data) mock_get_programs.return_value = data
GeneratedCertificateFactory( GeneratedCertificateFactory(
user=self.alice, user=self.alice,
...@@ -190,7 +181,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -190,7 +181,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_called_once_with(self.alice.username) mock_task.assert_called_once_with(self.alice.username)
def test_handle_mode_slugs(self, mock_task): def test_handle_mode_slugs(self, mock_task, mock_get_programs):
"""Verify that mode slugs are taken into account.""" """Verify that mode slugs are taken into account."""
data = [ data = [
factories.Program( factories.Program(
...@@ -205,7 +196,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -205,7 +196,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
] ]
), ),
] ]
self._mock_programs_api(data) mock_get_programs.return_value = data
GeneratedCertificateFactory( GeneratedCertificateFactory(
user=self.alice, user=self.alice,
...@@ -224,7 +215,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -224,7 +215,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_called_once_with(self.alice.username) mock_task.assert_called_once_with(self.alice.username)
def test_handle_passing_status(self, mock_task): def test_handle_passing_status(self, mock_task, mock_get_programs):
"""Verify that only certificates with a passing status are selected.""" """Verify that only certificates with a passing status are selected."""
data = [ data = [
factories.Program( factories.Program(
...@@ -237,7 +228,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -237,7 +228,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
] ]
), ),
] ]
self._mock_programs_api(data) mock_get_programs.return_value = data
passing_status = CertificateStatuses.downloadable passing_status = CertificateStatuses.downloadable
failing_status = CertificateStatuses.notpassing failing_status = CertificateStatuses.notpassing
...@@ -265,9 +256,8 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -265,9 +256,8 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_called_once_with(self.alice.username) mock_task.assert_called_once_with(self.alice.username)
def test_handle_missing_service_user(self, mock_task): def test_handle_missing_service_user(self, mock_task, __):
"""Verify that the command fails when no service user exists.""" """Verify that the command fails when no service user exists."""
self.catalog_integration = self.create_catalog_integration(service_username='test') self.catalog_integration = self.create_catalog_integration(service_username='test')
with self.assertRaises(CommandError): with self.assertRaises(CommandError):
call_command('backpopulate_program_credentials') call_command('backpopulate_program_credentials')
...@@ -275,7 +265,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -275,7 +265,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_not_called() mock_task.assert_not_called()
@mock.patch(COMMAND_MODULE + '.logger.exception') @mock.patch(COMMAND_MODULE + '.logger.exception')
def test_handle_enqueue_failure(self, mock_log, mock_task): def test_handle_enqueue_failure(self, mock_log, mock_task, mock_get_programs):
"""Verify that failure to enqueue a task doesn't halt execution.""" """Verify that failure to enqueue a task doesn't halt execution."""
def side_effect(username): def side_effect(username):
"""Simulate failure to enqueue a task.""" """Simulate failure to enqueue a task."""
...@@ -294,7 +284,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp ...@@ -294,7 +284,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
] ]
), ),
] ]
self._mock_programs_api(data) mock_get_programs.return_value = data
GeneratedCertificateFactory( GeneratedCertificateFactory(
user=self.alice, user=self.alice,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment