Skip to content

Commit 3591e87

Browse files
feat: add unified certificate task API with toggle, generate, and regenerate support
1 parent 7275ce1 commit 3591e87

4 files changed

Lines changed: 350 additions & 34 deletions

File tree

lms/djangoapps/instructor/tests/test_certificates.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,128 @@
3535
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
3636

3737

38+
@ddt.ddt
39+
class CertificateTaskViewTests(SharedModuleStoreTestCase):
40+
"""Tests for the certificate panel of the instructor dash. """
41+
42+
@classmethod
43+
def setUpClass(cls):
44+
"""
45+
Set up the test class with a test course and instructor dashboard URL.
46+
"""
47+
super().setUpClass()
48+
cls.course = CourseFactory.create()
49+
cls.url = reverse(
50+
'instructor_dashboard',
51+
kwargs={'course_id': str(cls.course.id)}
52+
)
53+
54+
def setUp(self):
55+
"""
56+
Set up test users and enable certificate generation configuration.
57+
"""
58+
super().setUp()
59+
self.user = UserFactory.create()
60+
self.global_staff = GlobalStaffFactory()
61+
self.instructor = InstructorFactory(course_key=self.course.id)
62+
63+
# Need to clear the cache for model-based configuration
64+
cache.clear()
65+
66+
# Enable the certificate generation feature
67+
CertificateGenerationConfiguration.objects.create(enabled=True)
68+
69+
def _login_as(self, role):
70+
"""
71+
Log in the test client as the specified user role.
72+
"""
73+
user_map = {
74+
"user": self.user.username,
75+
"instructor": self.instructor.username,
76+
"global_staff": self.global_staff.username
77+
}
78+
self.client.login(username=user_map.get(role, "user"), password=self.TEST_PASSWORD)
79+
80+
def _get_url(self, action):
81+
"""
82+
Build the unified certificate task URL for the given action.
83+
"""
84+
return reverse("certificate_task", kwargs={"course_id": self.course.id, "action": action})
85+
86+
def _assert_redirects_to_instructor_dash(self, response):
87+
"""Check that the response redirects to the certificates section. """
88+
expected_redirect = reverse(
89+
'instructor_dashboard',
90+
kwargs={'course_id': str(self.course.id)}
91+
)
92+
expected_redirect += '#view-certificates'
93+
self.assertRedirects(response, expected_redirect)
94+
95+
@ddt.data(True, False)
96+
def test_certificate_generation_enable(self, is_enabled):
97+
"""
98+
Test enabling or disabling self-generated certificates as global staff.
99+
"""
100+
self._login_as("global_staff")
101+
102+
params = {"certificates-enabled": "true" if is_enabled else "false"}
103+
response = self.client.post(
104+
self._get_url("toggle"),
105+
data=params
106+
)
107+
108+
# Expect a redirect back to the instructor dashboard
109+
self._assert_redirects_to_instructor_dash(response)
110+
111+
# Expect that certificate generation is now enabled for the course
112+
actual_enabled = certs_api.has_self_generated_certificates_enabled(str(self.course.id))
113+
assert is_enabled == actual_enabled
114+
115+
@ddt.data("user", "instructor", "global_staff")
116+
def test_certificate_generation(self, role):
117+
"""
118+
Test permission-based access to certificate generation by role.
119+
"""
120+
self._login_as(role)
121+
response = self.client.post(self._get_url("generate"))
122+
actual_status_code = {
123+
"user": 403,
124+
"instructor": 200,
125+
"global_staff": 200
126+
}
127+
assert response.status_code == actual_status_code[role]
128+
129+
@ddt.data(
130+
("downloadable", 200, True, 'Certificate regeneration task has been started. You can view '
131+
'the status of the generation task in the "Pending Tasks" section.'),
132+
("generating", 400, False, 'Please select certificate statuses that lie with '
133+
'in "certificate_statuses" entry in POST data.')
134+
)
135+
@ddt.unpack
136+
def test_certificate_regeneration_status_handling(self, cert_status, expected_status, success, expected_message):
137+
"""
138+
Test certificate regeneration with valid and invalid certificate statuses.
139+
"""
140+
# Create a certificate with the given status
141+
GeneratedCertificateFactory.create(
142+
user=self.user,
143+
course_id=self.course.id,
144+
status=cert_status,
145+
mode='honor'
146+
)
147+
148+
self._login_as("global_staff")
149+
response = self.client.post(
150+
self._get_url("regenerate"),
151+
data={'certificate_statuses': [cert_status]},
152+
)
153+
154+
assert response.status_code == expected_status
155+
res_json = response.json()
156+
assert res_json.get('success', False) is success
157+
assert res_json.get('message') == expected_message
158+
159+
38160
@ddt.ddt
39161
class CertificatesInstructorDashTest(SharedModuleStoreTestCase):
40162
"""Tests for the certificate panel of the instructor dash. """
@@ -138,7 +260,11 @@ def test_show_enabled_button_for_html_certs(self):
138260
self.assertContains(response, 'enable-certificates-submit')
139261
self.assertNotContains(response, 'Generate Example Certificates')
140262

141-
@mock.patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True})
263+
@mock.patch.dict(settings.FEATURES, {
264+
'CERTIFICATES_HTML_VIEW': True,
265+
'CERTIFICATES_INSTRUCTOR_GENERATION': False
266+
}
267+
)
142268
def test_buttons_for_html_certs_in_self_paced_course(self):
143269
"""
144270
Tests `Enable Student-Generated Certificates` button is enabled

0 commit comments

Comments
 (0)