[bug] Corrected issue where program dash showed incorrect completed
count [MICROBA-1163] This change will correct an issue in the Program Dashboard where a user would see a course as completed, but not see their Certificate because it was not available to them yet.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"""Tests covering Programs utilities."""
|
||||
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import uuid
|
||||
@@ -9,6 +8,7 @@ from copy import deepcopy
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
from edx_toggles.toggles import LegacyWaffleSwitch
|
||||
from edx_toggles.toggles.testutils import override_waffle_switch
|
||||
import httpretty
|
||||
from django.conf import settings
|
||||
@@ -34,6 +34,8 @@ from openedx.core.djangoapps.catalog.tests.factories import (
|
||||
SeatFactory,
|
||||
generate_course_run_key
|
||||
)
|
||||
from openedx.core.djangoapps.certificates.config import waffle
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.programs import ALWAYS_CALCULATE_PROGRAM_PRICE_AS_ANONYMOUS_USER
|
||||
from openedx.core.djangoapps.programs.tests.factories import ProgressFactory
|
||||
from openedx.core.djangoapps.programs.utils import (
|
||||
@@ -58,12 +60,14 @@ from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCour
|
||||
ECOMMERCE_URL_ROOT = 'https://ecommerce.example.com'
|
||||
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
LOGGER_NAME = 'openedx.core.djangoapps.programs.utils'
|
||||
AUTO_CERTIFICATE_GENERATION_SWITCH = LegacyWaffleSwitch(waffle.waffle(), waffle.AUTO_CERTIFICATE_GENERATION) # pylint: disable=toggle-missing-annotation
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
@override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True)
|
||||
@mock.patch(UTILS_MODULE + '.get_programs')
|
||||
class TestProgramProgressMeter(TestCase):
|
||||
class TestProgramProgressMeter(ModuleStoreTestCase):
|
||||
"""Tests of the program progress utility class."""
|
||||
|
||||
def setUp(self):
|
||||
@@ -473,7 +477,32 @@ class TestProgramProgressMeter(TestCase):
|
||||
|
||||
def test_simulate_progress(self, mock_get_programs):
|
||||
"""Simulate the entirety of a user's progress through a program."""
|
||||
first_course_run_key, second_course_run_key = (generate_course_run_key() for __ in range(2))
|
||||
today = datetime.datetime.now(utc)
|
||||
two_days_ago = today - datetime.timedelta(days=2)
|
||||
three_days_ago = today - datetime.timedelta(days=3)
|
||||
yesterday = today - datetime.timedelta(days=1)
|
||||
tomorrow = today + datetime.timedelta(days=1)
|
||||
course1 = ModuleStoreCourseFactory.create(
|
||||
start=yesterday,
|
||||
end=tomorrow,
|
||||
self_paced=True,
|
||||
)
|
||||
first_course_run_key = str(course1.id)
|
||||
course2 = ModuleStoreCourseFactory.create(
|
||||
start=yesterday,
|
||||
end=tomorrow,
|
||||
self_paced=True,
|
||||
)
|
||||
second_course_run_key = str(course2.id)
|
||||
course3 = ModuleStoreCourseFactory.create(
|
||||
start=three_days_ago,
|
||||
end=two_days_ago,
|
||||
self_paced=False,
|
||||
certificate_available_date=tomorrow,
|
||||
certificates_display_behavior='end'
|
||||
)
|
||||
third_course_run_key = str(course3.id)
|
||||
|
||||
data = [
|
||||
ProgramFactory(
|
||||
courses=[
|
||||
@@ -483,6 +512,9 @@ class TestProgramProgressMeter(TestCase):
|
||||
CourseFactory(course_runs=[
|
||||
CourseRunFactory(key=second_course_run_key),
|
||||
]),
|
||||
CourseFactory(course_runs=[
|
||||
CourseRunFactory(key=third_course_run_key),
|
||||
]),
|
||||
]
|
||||
),
|
||||
ProgramFactory(),
|
||||
@@ -500,18 +532,19 @@ class TestProgramProgressMeter(TestCase):
|
||||
_, program_uuid = data[0], data[0]['uuid']
|
||||
self._assert_progress(
|
||||
meter,
|
||||
ProgressFactory(uuid=program_uuid, in_progress=1, not_started=1)
|
||||
ProgressFactory(uuid=program_uuid, in_progress=1, not_started=2)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == []
|
||||
|
||||
# Two enrollments, all courses in progress.
|
||||
# 3 enrollments, 3 courses in progress.
|
||||
self._create_enrollments(second_course_run_key)
|
||||
self._create_enrollments(third_course_run_key)
|
||||
meter = ProgramProgressMeter(self.site, self.user)
|
||||
self._assert_progress(
|
||||
meter,
|
||||
ProgressFactory(
|
||||
uuid=program_uuid,
|
||||
in_progress=2,
|
||||
in_progress=3,
|
||||
)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == []
|
||||
@@ -524,7 +557,7 @@ class TestProgramProgressMeter(TestCase):
|
||||
ProgressFactory(
|
||||
uuid=program_uuid,
|
||||
completed=1,
|
||||
in_progress=1,
|
||||
in_progress=2,
|
||||
)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == []
|
||||
@@ -538,12 +571,12 @@ class TestProgramProgressMeter(TestCase):
|
||||
ProgressFactory(
|
||||
uuid=program_uuid,
|
||||
completed=1,
|
||||
in_progress=1,
|
||||
in_progress=2,
|
||||
)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == []
|
||||
|
||||
# Second valid certificate obtained, all courses complete.
|
||||
# Second valid certificate obtained, 2 courses complete.
|
||||
second_cert.mode = MODES.verified
|
||||
second_cert.save()
|
||||
meter = ProgramProgressMeter(self.site, self.user)
|
||||
@@ -552,25 +585,58 @@ class TestProgramProgressMeter(TestCase):
|
||||
ProgressFactory(
|
||||
uuid=program_uuid,
|
||||
completed=2,
|
||||
in_progress=1,
|
||||
)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == []
|
||||
|
||||
# 3 certs, 1 unavailable, Program available in the future
|
||||
self._create_certificates(third_course_run_key, mode=MODES.verified)
|
||||
meter = ProgramProgressMeter(self.site, self.user)
|
||||
self._assert_progress(
|
||||
meter,
|
||||
ProgressFactory(
|
||||
uuid=program_uuid,
|
||||
completed=2,
|
||||
in_progress=1,
|
||||
)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == [program_uuid]
|
||||
assert meter.completed_programs_with_available_dates[program_uuid] > today
|
||||
|
||||
# 3 certs, all available, program cert in the past/now
|
||||
course3_overview = CourseOverview.get_from_id(course3.id)
|
||||
course3_overview.certificate_available_date = yesterday
|
||||
course3_overview.save()
|
||||
meter = ProgramProgressMeter(self.site, self.user)
|
||||
self._assert_progress(
|
||||
meter,
|
||||
ProgressFactory(
|
||||
uuid=program_uuid,
|
||||
completed=3,
|
||||
)
|
||||
)
|
||||
assert list(meter.completed_programs_with_available_dates.keys()) == [program_uuid]
|
||||
assert meter.completed_programs_with_available_dates[program_uuid].date() == today.date()
|
||||
|
||||
def test_nonverified_course_run_completion(self, mock_get_programs):
|
||||
"""
|
||||
Course runs aren't necessarily of type verified. Verify that a program can
|
||||
still be completed when this is the case.
|
||||
"""
|
||||
course_run_key = generate_course_run_key()
|
||||
course1 = ModuleStoreCourseFactory.create(self_paced=True, )
|
||||
course_run_key = str(course1.id)
|
||||
course2 = ModuleStoreCourseFactory.create(self_paced=True, )
|
||||
program = ProgramFactory(
|
||||
courses=[
|
||||
CourseFactory(course_runs=[
|
||||
CourseRunFactory(key=course_run_key, type='honor'),
|
||||
CourseRunFactory(key=str(course2.id)),
|
||||
]),
|
||||
]
|
||||
)
|
||||
data = [
|
||||
ProgramFactory(
|
||||
courses=[
|
||||
CourseFactory(course_runs=[
|
||||
CourseRunFactory(key=course_run_key, type='honor'),
|
||||
CourseRunFactory(),
|
||||
]),
|
||||
]
|
||||
),
|
||||
program,
|
||||
ProgramFactory(),
|
||||
]
|
||||
mock_get_programs.return_value = data
|
||||
@@ -579,7 +645,7 @@ class TestProgramProgressMeter(TestCase):
|
||||
self._create_certificates(course_run_key)
|
||||
meter = ProgramProgressMeter(self.site, self.user)
|
||||
|
||||
_, program_uuid = data[0], data[0]['uuid']
|
||||
program_uuid = program['uuid']
|
||||
self._assert_progress(
|
||||
meter,
|
||||
ProgressFactory(uuid=program_uuid, completed=1)
|
||||
@@ -623,6 +689,7 @@ class TestProgramProgressMeter(TestCase):
|
||||
if str(cert.course_id) == run_course2['key']:
|
||||
return datetime.datetime(2016, 1, 1)
|
||||
return datetime.datetime(2015, 1, 1)
|
||||
|
||||
mock_available_date_for_certificate.side_effect = available_date_fake
|
||||
|
||||
meter = ProgramProgressMeter(self.site, self.user)
|
||||
@@ -635,14 +702,21 @@ class TestProgramProgressMeter(TestCase):
|
||||
"""
|
||||
Verify that the method can find course run certificates when not mocked out.
|
||||
"""
|
||||
downloadable = CourseRunFactory()
|
||||
generating = CourseRunFactory()
|
||||
downloadable_module_store_course = ModuleStoreCourseFactory.create(self_paced=True, )
|
||||
downloadable = CourseRunFactory(key=downloadable_module_store_course.id)
|
||||
course_availability_in_future = CourseRunFactory()
|
||||
generating_module_store_course = ModuleStoreCourseFactory.create(self_paced=True, )
|
||||
generating = CourseRunFactory(key=generating_module_store_course.id)
|
||||
unknown = CourseRunFactory()
|
||||
course = CourseFactory(course_runs=[downloadable, generating, unknown])
|
||||
course = CourseFactory(course_runs=[downloadable, course_availability_in_future, generating, unknown])
|
||||
program = ProgramFactory(courses=[course])
|
||||
mock_get_programs.return_value = [program]
|
||||
|
||||
self._create_enrollments(downloadable['key'], generating['key'], unknown['key'])
|
||||
self._create_enrollments(
|
||||
downloadable['key'],
|
||||
generating['key'],
|
||||
unknown['key']
|
||||
)
|
||||
|
||||
self._create_certificates(downloadable['key'], mode=CourseMode.VERIFIED)
|
||||
self._create_certificates(generating['key'], status='generating', mode=CourseMode.HONOR)
|
||||
@@ -652,8 +726,8 @@ class TestProgramProgressMeter(TestCase):
|
||||
self.assertCountEqual(
|
||||
meter.completed_course_runs,
|
||||
[
|
||||
{'course_run_id': downloadable['key'], 'type': CourseMode.VERIFIED},
|
||||
{'course_run_id': generating['key'], 'type': CourseMode.HONOR},
|
||||
{'course_run_id': str(downloadable['key']), 'type': CourseMode.VERIFIED},
|
||||
{'course_run_id': str(generating['key']), 'type': CourseMode.HONOR},
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1243,6 +1317,7 @@ class TestGetCertificates(TestCase):
|
||||
"""
|
||||
Tests of the function used to get certificates associated with a program.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
|
||||
@@ -426,6 +426,9 @@ class ProgramProgressMeter:
|
||||
"""
|
||||
Determine which course runs have been completed and failed by the user.
|
||||
|
||||
A course run is considered completed for a user if they have a certificate in the correct state and
|
||||
the certificate is available.
|
||||
|
||||
Returns:
|
||||
dict with a list of completed and failed runs
|
||||
"""
|
||||
@@ -433,12 +436,16 @@ class ProgramProgressMeter:
|
||||
|
||||
completed_runs, failed_runs = [], []
|
||||
for certificate in course_run_certificates:
|
||||
course_key = certificate['course_key']
|
||||
course_data = {
|
||||
'course_run_id': str(certificate['course_key']),
|
||||
'course_run_id': str(course_key),
|
||||
'type': self._certificate_mode_translation(certificate['type']),
|
||||
}
|
||||
|
||||
if certificate_api.is_passing_status(certificate['status']):
|
||||
if (
|
||||
certificate_api.is_passing_status(certificate['status'])
|
||||
and CourseOverview.get_from_id(course_key).may_certify()
|
||||
):
|
||||
completed_runs.append(course_data)
|
||||
else:
|
||||
failed_runs.append(course_data)
|
||||
|
||||
Reference in New Issue
Block a user