|
17 | 17 | from rest_framework.test import APIClient, APITestCase |
18 | 18 |
|
19 | 19 | from common.djangoapps.course_modes.tests.factories import CourseModeFactory |
20 | | -from common.djangoapps.student.models.course_enrollment import CourseEnrollment |
| 20 | +from common.djangoapps.course_modes.models import CourseMode |
| 21 | +from common.djangoapps.student.models import ManualEnrollmentAudit |
| 22 | +from common.djangoapps.student.models.course_enrollment import CourseEnrollment, CourseEnrollmentAllowed |
21 | 23 | from common.djangoapps.student.roles import CourseBetaTesterRole, CourseDataResearcherRole, CourseInstructorRole |
22 | 24 | from common.djangoapps.student.tests.factories import ( |
23 | 25 | AdminFactory, |
@@ -2049,3 +2051,248 @@ def test_filter_beta_testers_with_search(self): |
2049 | 2051 | data = response.data |
2050 | 2052 | self.assertEqual(data['count'], 1) |
2051 | 2053 | self.assertTrue(data['results'][0]['is_beta_tester']) |
| 2054 | + |
| 2055 | + |
| 2056 | +class EnrollmentStatusViewTest(SharedModuleStoreTestCase): |
| 2057 | + """Tests for the EnrollmentStatusView v2 GET endpoint.""" |
| 2058 | + |
| 2059 | + @classmethod |
| 2060 | + def setUpClass(cls): |
| 2061 | + super().setUpClass() |
| 2062 | + cls.course = CourseFactory.create() |
| 2063 | + |
| 2064 | + def setUp(self): |
| 2065 | + super().setUp() |
| 2066 | + self.client = APIClient() |
| 2067 | + self.instructor = InstructorFactory(course_key=self.course.id) |
| 2068 | + self.url = reverse( |
| 2069 | + 'instructor_api_v2:enrollment_status', |
| 2070 | + kwargs={'course_id': str(self.course.id)} |
| 2071 | + ) |
| 2072 | + |
| 2073 | + def test_unauthenticated_returns_401(self): |
| 2074 | + response = self.client.get(self.url, {'email_or_username': 'test'}) |
| 2075 | + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) |
| 2076 | + |
| 2077 | + def test_student_returns_403(self): |
| 2078 | + student = UserFactory() |
| 2079 | + self.client.force_authenticate(user=student) |
| 2080 | + response = self.client.get(self.url, {'email_or_username': 'test'}) |
| 2081 | + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) |
| 2082 | + |
| 2083 | + def test_missing_identifier_returns_400(self): |
| 2084 | + self.client.force_authenticate(user=self.instructor) |
| 2085 | + response = self.client.get(self.url) |
| 2086 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 2087 | + |
| 2088 | + def test_empty_identifier_returns_400(self): |
| 2089 | + self.client.force_authenticate(user=self.instructor) |
| 2090 | + response = self.client.get(self.url, {'email_or_username': ''}) |
| 2091 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 2092 | + |
| 2093 | + def test_active_enrollment(self): |
| 2094 | + learner = UserFactory() |
| 2095 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, is_active=True) |
| 2096 | + self.client.force_authenticate(user=self.instructor) |
| 2097 | + response = self.client.get(self.url, {'email_or_username': learner.email}) |
| 2098 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2099 | + data = response.data |
| 2100 | + self.assertEqual(data['enrollment_status'], 'active') |
| 2101 | + self.assertIsNotNone(data['mode']) |
| 2102 | + self.assertEqual(data['course_id'], str(self.course.id)) |
| 2103 | + self.assertEqual(data['email_or_username'], learner.email) |
| 2104 | + self.assertIn('active', data['message']) |
| 2105 | + |
| 2106 | + def test_inactive_enrollment(self): |
| 2107 | + learner = UserFactory() |
| 2108 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, is_active=False) |
| 2109 | + self.client.force_authenticate(user=self.instructor) |
| 2110 | + response = self.client.get(self.url, {'email_or_username': learner.username}) |
| 2111 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2112 | + self.assertEqual(response.data['enrollment_status'], 'inactive') |
| 2113 | + |
| 2114 | + def test_pending_enrollment(self): |
| 2115 | + |
| 2116 | + CourseEnrollmentAllowed.objects.create( |
| 2117 | + email=email, |
| 2118 | + course_id=self.course.id, |
| 2119 | + ) |
| 2120 | + self.client.force_authenticate(user=self.instructor) |
| 2121 | + response = self.client.get(self.url, {'email_or_username': email}) |
| 2122 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2123 | + self.assertEqual(response.data['enrollment_status'], 'pending') |
| 2124 | + self.assertIsNone(response.data['mode']) |
| 2125 | + |
| 2126 | + def test_never_enrolled_existing_user(self): |
| 2127 | + learner = UserFactory() |
| 2128 | + self.client.force_authenticate(user=self.instructor) |
| 2129 | + response = self.client.get(self.url, {'email_or_username': learner.username}) |
| 2130 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2131 | + self.assertEqual(response.data['enrollment_status'], 'never_enrolled') |
| 2132 | + self.assertIsNone(response.data['mode']) |
| 2133 | + |
| 2134 | + def test_never_enrolled_nonexistent_user(self): |
| 2135 | + self.client.force_authenticate(user=self.instructor) |
| 2136 | + response = self. client. get( self. url, { 'email_or_username': '[email protected]'}) |
| 2137 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2138 | + self.assertEqual(response.data['enrollment_status'], 'never_enrolled') |
| 2139 | + |
| 2140 | + def test_lookup_by_username(self): |
| 2141 | + learner = UserFactory() |
| 2142 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, is_active=True) |
| 2143 | + self.client.force_authenticate(user=self.instructor) |
| 2144 | + response = self.client.get(self.url, {'email_or_username': learner.username}) |
| 2145 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2146 | + self.assertEqual(response.data['enrollment_status'], 'active') |
| 2147 | + |
| 2148 | + def test_staff_can_access(self): |
| 2149 | + staff = StaffFactory(course_key=self.course.id) |
| 2150 | + learner = UserFactory() |
| 2151 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, is_active=True) |
| 2152 | + self.client.force_authenticate(user=staff) |
| 2153 | + response = self.client.get(self.url, {'email_or_username': learner.email}) |
| 2154 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2155 | + |
| 2156 | + |
| 2157 | +class ChangeEnrollmentModeViewTest(SharedModuleStoreTestCase): |
| 2158 | + """Tests for the ChangeEnrollmentModeView v2 POST endpoint.""" |
| 2159 | + |
| 2160 | + @classmethod |
| 2161 | + def setUpClass(cls): |
| 2162 | + super().setUpClass() |
| 2163 | + cls.course = CourseFactory.create() |
| 2164 | + |
| 2165 | + def setUp(self): |
| 2166 | + super().setUp() |
| 2167 | + self.client = APIClient() |
| 2168 | + self.instructor = InstructorFactory(course_key=self.course.id) |
| 2169 | + self.url = reverse( |
| 2170 | + 'instructor_api_v2:change_enrollment_mode', |
| 2171 | + kwargs={'course_id': str(self.course.id)} |
| 2172 | + ) |
| 2173 | + # Ensure audit and verified modes exist |
| 2174 | + CourseModeFactory(course_id=self.course.id, mode_slug='audit') |
| 2175 | + CourseModeFactory(course_id=self.course.id, mode_slug='verified') |
| 2176 | + |
| 2177 | + def test_unauthenticated_returns_401(self): |
| 2178 | + response = self.client.post(self.url, {'email_or_username': 'x', 'mode': 'verified'}) |
| 2179 | + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) |
| 2180 | + |
| 2181 | + def test_student_returns_403(self): |
| 2182 | + student = UserFactory() |
| 2183 | + self.client.force_authenticate(user=student) |
| 2184 | + response = self.client.post(self.url, {'email_or_username': 'x', 'mode': 'verified'}) |
| 2185 | + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) |
| 2186 | + |
| 2187 | + def test_missing_fields_returns_400(self): |
| 2188 | + self.client.force_authenticate(user=self.instructor) |
| 2189 | + response = self.client.post(self.url, {}) |
| 2190 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 2191 | + |
| 2192 | + def test_nonexistent_user_returns_404(self): |
| 2193 | + self.client.force_authenticate(user=self.instructor) |
| 2194 | + response = self.client.post(self.url, { |
| 2195 | + 'email_or_username': '[email protected]', |
| 2196 | + 'mode': 'verified', |
| 2197 | + }) |
| 2198 | + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) |
| 2199 | + |
| 2200 | + def test_user_not_enrolled_returns_404(self): |
| 2201 | + learner = UserFactory() |
| 2202 | + self.client.force_authenticate(user=self.instructor) |
| 2203 | + response = self.client.post(self.url, { |
| 2204 | + 'email_or_username': learner.email, |
| 2205 | + 'mode': 'verified', |
| 2206 | + }) |
| 2207 | + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) |
| 2208 | + |
| 2209 | + def test_inactive_enrollment_returns_404(self): |
| 2210 | + learner = UserFactory() |
| 2211 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, is_active=False) |
| 2212 | + self.client.force_authenticate(user=self.instructor) |
| 2213 | + response = self.client.post(self.url, { |
| 2214 | + 'email_or_username': learner.email, |
| 2215 | + 'mode': 'verified', |
| 2216 | + }) |
| 2217 | + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) |
| 2218 | + |
| 2219 | + def test_invalid_mode_returns_400(self): |
| 2220 | + learner = UserFactory() |
| 2221 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, mode='audit') |
| 2222 | + self.client.force_authenticate(user=self.instructor) |
| 2223 | + response = self.client.post(self.url, { |
| 2224 | + 'email_or_username': learner.email, |
| 2225 | + 'mode': 'nonexistent_mode', |
| 2226 | + }) |
| 2227 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 2228 | + self.assertIn('Invalid mode', response.data['error']) |
| 2229 | + |
| 2230 | + def test_same_mode_returns_400(self): |
| 2231 | + learner = UserFactory() |
| 2232 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, mode='audit') |
| 2233 | + self.client.force_authenticate(user=self.instructor) |
| 2234 | + response = self.client.post(self.url, { |
| 2235 | + 'email_or_username': learner.email, |
| 2236 | + 'mode': 'audit', |
| 2237 | + }) |
| 2238 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 2239 | + self.assertIn('already enrolled', response.data['error']) |
| 2240 | + |
| 2241 | + def test_successful_mode_change(self): |
| 2242 | + learner = UserFactory() |
| 2243 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, mode='audit') |
| 2244 | + self.client.force_authenticate(user=self.instructor) |
| 2245 | + response = self.client.post(self.url, { |
| 2246 | + 'email_or_username': learner.email, |
| 2247 | + 'mode': 'verified', |
| 2248 | + }) |
| 2249 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2250 | + data = response.data |
| 2251 | + self.assertEqual(data['old_mode'], 'audit') |
| 2252 | + self.assertEqual(data['new_mode'], 'verified') |
| 2253 | + self.assertEqual(data['course_id'], str(self.course.id)) |
| 2254 | + self.assertEqual(data['email_or_username'], learner.email) |
| 2255 | + |
| 2256 | + # Verify the enrollment was actually changed |
| 2257 | + enrollment = CourseEnrollment.get_enrollment(learner, self.course.id) |
| 2258 | + self.assertEqual(enrollment.mode, 'verified') |
| 2259 | + |
| 2260 | + def test_audit_trail_created(self): |
| 2261 | + learner = UserFactory() |
| 2262 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, mode='audit') |
| 2263 | + self.client.force_authenticate(user=self.instructor) |
| 2264 | + response = self.client.post(self.url, { |
| 2265 | + 'email_or_username': learner.email, |
| 2266 | + 'mode': 'verified', |
| 2267 | + 'reason': 'Student upgraded', |
| 2268 | + }) |
| 2269 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2270 | + |
| 2271 | + enrollment = CourseEnrollment.get_enrollment(learner, self.course.id) |
| 2272 | + audit = ManualEnrollmentAudit.objects.filter(enrollment=enrollment).first() |
| 2273 | + self.assertIsNotNone(audit) |
| 2274 | + self.assertEqual(audit.enrolled_email, learner.email) |
| 2275 | + self.assertIn('mode changed from audit to verified', audit.state_transition) |
| 2276 | + self.assertEqual(audit.reason, 'Student upgraded') |
| 2277 | + |
| 2278 | + def test_lookup_by_username(self): |
| 2279 | + learner = UserFactory() |
| 2280 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, mode='audit') |
| 2281 | + self.client.force_authenticate(user=self.instructor) |
| 2282 | + response = self.client.post(self.url, { |
| 2283 | + 'email_or_username': learner.username, |
| 2284 | + 'mode': 'verified', |
| 2285 | + }) |
| 2286 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 2287 | + self.assertEqual(response.data['new_mode'], 'verified') |
| 2288 | + |
| 2289 | + def test_staff_can_access(self): |
| 2290 | + staff = StaffFactory(course_key=self.course.id) |
| 2291 | + learner = UserFactory() |
| 2292 | + CourseEnrollmentFactory(user=learner, course_id=self.course.id, mode='audit') |
| 2293 | + self.client.force_authenticate(user=staff) |
| 2294 | + response = self.client.post(self.url, { |
| 2295 | + 'email_or_username': learner.email, |
| 2296 | + 'mode': 'verified', |
| 2297 | + }) |
| 2298 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
0 commit comments