Use the catalog for program certificate backpopulation

This command is used to backfill missing program certificates. Formerly, it used the programs service. It now uses the catalog service exclusively.

ECOM-4422
This commit is contained in:
Renzo Lucioni
2017-01-27 15:02:51 -05:00
parent 327ffa92a0
commit 4027dd8444
3 changed files with 92 additions and 115 deletions

View File

@@ -1,16 +1,13 @@
"""Tests for the backpopulate_program_credentials management command."""
import json
import ddt
from django.core.management import call_command, CommandError
from django.core.management import call_command
from django.test import TestCase
import httpretty
import mock
from certificates.models import CertificateStatuses # pylint: disable=import-error
from lms.djangoapps.certificates.api import MODES
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
from openedx.core.djangoapps.programs.tests import factories
from openedx.core.djangoapps.catalog.tests import factories
from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin
from openedx.core.djangolib.testing.utils import skip_unless_lms
@@ -26,7 +23,7 @@ COMMAND_MODULE = 'openedx.core.djangoapps.programs.management.commands.backpopul
@skip_unless_lms
class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsApiConfigMixin, TestCase):
"""Tests for the backpopulate_program_credentials management command."""
course_id, alternate_course_id = 'org/course/run', 'org/alternate/run'
course_run_key, alternate_course_run_key = (factories.generate_course_run_key() for __ in range(2))
def setUp(self):
super(BackpopulateProgramCredentialsTests, self).setUp()
@@ -44,13 +41,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
@ddt.data(True, False)
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 = [
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=self.course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=self.course_run_key),
]),
]
),
@@ -59,14 +57,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
GeneratedCertificateFactory(
user=self.alice,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
GeneratedCertificateFactory(
user=self.bob,
course_id=self.alternate_course_id,
course_id=self.alternate_course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
@@ -81,42 +79,38 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
@ddt.data(
[
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=course_run_key),
]),
]
),
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=alternate_course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=alternate_course_run_key),
]),
]
),
],
[
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=course_run_key),
]),
factories.CourseCode(run_modes=[
factories.RunMode(course_key=alternate_course_id),
factories.Course(course_runs=[
factories.CourseRun(key=alternate_course_run_key),
]),
]
),
],
[
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=course_id),
factories.RunMode(course_key=alternate_course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=course_run_key),
factories.CourseRun(key=alternate_course_run_key),
]),
]
),
@@ -128,14 +122,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
GeneratedCertificateFactory(
user=self.alice,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
GeneratedCertificateFactory(
user=self.bob,
course_id=self.alternate_course_id,
course_id=self.alternate_course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
@@ -149,14 +143,16 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_has_calls(calls, any_order=True)
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
course run certificates.
"""
data = [
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=self.course_id),
factories.RunMode(course_key=self.alternate_course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=self.course_run_key),
factories.CourseRun(key=self.alternate_course_run_key),
]),
]
),
@@ -165,14 +161,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
GeneratedCertificateFactory(
user=self.alice,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
GeneratedCertificateFactory(
user=self.alice,
course_id=self.alternate_course_id,
course_id=self.alternate_course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
@@ -182,16 +178,15 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_called_once_with(self.alice.username)
def test_handle_mode_slugs(self, mock_task, mock_get_programs):
"""Verify that mode slugs are taken into account."""
"""
Verify that course run types are taken into account when identifying
qualifying course run certificates.
"""
data = [
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(
course_key=self.course_id,
mode_slug=MODES.honor
),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=self.course_run_key, type='honor'),
]),
]
),
@@ -200,13 +195,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
GeneratedCertificateFactory(
user=self.alice,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.honor,
status=CertificateStatuses.downloadable,
)
GeneratedCertificateFactory(
user=self.bob,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
@@ -216,14 +212,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_called_once_with(self.alice.username)
def test_handle_passing_status(self, mock_task, mock_get_programs):
"""Verify that only certificates with a passing status are selected."""
"""
Verify that only course run certificates with a passing status are selected.
"""
data = [
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=self.course_id),
factories.RunMode(course_key=self.alternate_course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=self.course_run_key),
]),
]
),
@@ -238,16 +234,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
GeneratedCertificateFactory(
user=self.alice,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=passing_status,
)
# The alternate course is used here to verify that the status and run_mode
# queries are being ANDed together correctly.
GeneratedCertificateFactory(
user=self.bob,
course_id=self.alternate_course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=failing_status,
)
@@ -256,14 +250,6 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
mock_task.assert_called_once_with(self.alice.username)
def test_handle_missing_service_user(self, mock_task, __):
"""Verify that the command fails when no service user exists."""
self.catalog_integration = self.create_catalog_integration(service_username='test')
with self.assertRaises(CommandError):
call_command('backpopulate_program_credentials')
mock_task.assert_not_called()
@mock.patch(COMMAND_MODULE + '.logger.exception')
def test_handle_enqueue_failure(self, mock_log, mock_task, mock_get_programs):
"""Verify that failure to enqueue a task doesn't halt execution."""
@@ -276,10 +262,9 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
data = [
factories.Program(
organizations=[factories.Organization()],
course_codes=[
factories.CourseCode(run_modes=[
factories.RunMode(course_key=self.course_id),
courses=[
factories.Course(course_runs=[
factories.CourseRun(key=self.course_run_key),
]),
]
),
@@ -288,14 +273,14 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp
GeneratedCertificateFactory(
user=self.alice,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)
GeneratedCertificateFactory(
user=self.bob,
course_id=self.course_id,
course_id=self.course_run_key,
mode=MODES.verified,
status=CertificateStatuses.downloadable,
)