@@ -2187,3 +2187,191 @@ def test_get_no_enrollment_returns_null(self):
21872187 )
21882188 assert resp .status_code == status .HTTP_200_OK
21892189 assert resp .json () is None
2190+
2191+
2192+ @skip_unless_lms
2193+ class EnrollmentCourseDetailViewResponseShapeTest (ModuleStoreTestCase , APITestCase ):
2194+ """
2195+ Tests that verify EnrollmentCourseDetailView (GET /enrollment/v1/course/{course_id})
2196+ response structure is preserved after migrating to CourseSerializer + direct ORM (ADR 0025).
2197+ """
2198+
2199+ def setUp (self ):
2200+ super ().setUp ()
2201+ self .course = CourseFactory .create (emit_signals = True )
2202+ CourseModeFactory .create (
2203+ course_id = self .course .id ,
2204+ mode_slug = CourseMode .DEFAULT_MODE_SLUG ,
2205+ mode_display_name = CourseMode .DEFAULT_MODE_SLUG ,
2206+ )
2207+
2208+ def _get_course_details (self , course_id = None , include_expired = False ):
2209+ url = reverse ('courseenrollmentdetails' , kwargs = {'course_id' : course_id or str (self .course .id )})
2210+ if include_expired :
2211+ url += '?include_expired=1'
2212+ return self .client .get (url )
2213+
2214+ def test_returns_200 (self ):
2215+ assert self ._get_course_details ().status_code == status .HTTP_200_OK
2216+
2217+ def test_response_top_level_fields (self ):
2218+ """Response contains the expected top-level CourseSerializer fields."""
2219+ body = self ._get_course_details ().json ()
2220+ for field in ('course_id' , 'course_name' , 'enrollment_start' , 'enrollment_end' ,
2221+ 'course_start' , 'course_end' , 'invite_only' , 'course_modes' , 'pacing_type' ):
2222+ assert field in body , f"Missing field: { field } "
2223+
2224+ def test_course_id_matches_requested_course (self ):
2225+ body = self ._get_course_details ().json ()
2226+ assert body ['course_id' ] == str (self .course .id )
2227+
2228+ def test_course_modes_is_list (self ):
2229+ body = self ._get_course_details ().json ()
2230+ assert isinstance (body ['course_modes' ], list )
2231+
2232+ def test_course_mode_fields (self ):
2233+ """Each mode entry contains the expected fields."""
2234+ body = self ._get_course_details ().json ()
2235+ mode = body ['course_modes' ][0 ]
2236+ for field in ('slug' , 'name' , 'min_price' , 'suggested_prices' , 'currency' ,
2237+ 'expiration_datetime' , 'description' , 'sku' , 'bulk_sku' ):
2238+ assert field in mode , f"Missing course_mode field: { field } "
2239+
2240+ def test_invalid_course_id_returns_400 (self ):
2241+ resp = self ._get_course_details (course_id = 'not/a/real/course' )
2242+ assert resp .status_code == status .HTTP_400_BAD_REQUEST
2243+
2244+ def test_nonexistent_course_returns_400 (self ):
2245+ resp = self ._get_course_details (course_id = 'course-v1:Org+NonExistent+2099' )
2246+ assert resp .status_code == status .HTTP_400_BAD_REQUEST
2247+
2248+
2249+ @skip_unless_lms
2250+ class EnrollmentListViewResponseShapeTest (ModuleStoreTestCase , APITestCase ):
2251+ """
2252+ Tests that verify EnrollmentListView (GET /enrollment/v1/enrollment)
2253+ response structure is preserved after migrating to CourseEnrollmentSerializer + ORM (ADR 0025).
2254+ """
2255+
2256+ USERNAME = "TestLearner"
2257+ PASSWORD = "edx"
2258+
2259+ def setUp (self ):
2260+ super ().setUp ()
2261+ self .course = CourseFactory .create (emit_signals = True )
2262+ CourseModeFactory .create (
2263+ course_id = self .course .id ,
2264+ mode_slug = CourseMode .DEFAULT_MODE_SLUG ,
2265+ mode_display_name = CourseMode .DEFAULT_MODE_SLUG ,
2266+ )
2267+ self .user = UserFactory .create (username = self .USERNAME , password = self .PASSWORD )
2268+ self .client .login (username = self .USERNAME , password = self .PASSWORD )
2269+ CourseEnrollment .enroll (self .user , self .course .id )
2270+
2271+ def _get_enrollments (self , user = None ):
2272+ url = reverse ('courseenrollments' )
2273+ if user :
2274+ url += f'?user={ user } '
2275+ return self .client .get (url )
2276+
2277+ def test_returns_200 (self ):
2278+ assert self ._get_enrollments ().status_code == status .HTTP_200_OK
2279+
2280+ def test_response_is_list (self ):
2281+ body = self ._get_enrollments ().json ()
2282+ assert isinstance (body , list )
2283+
2284+ def test_enrollment_top_level_fields (self ):
2285+ """Each enrollment entry contains the expected top-level fields."""
2286+ body = self ._get_enrollments ().json ()
2287+ assert len (body ) >= 1
2288+ entry = body [0 ]
2289+ for field in ('created' , 'mode' , 'is_active' , 'user' , 'course_details' ):
2290+ assert field in entry , f"Missing top-level field: { field } "
2291+
2292+ def test_enrollment_user_and_mode_values (self ):
2293+ body = self ._get_enrollments ().json ()
2294+ entry = body [0 ]
2295+ assert entry ['user' ] == self .USERNAME
2296+ assert entry ['mode' ] == CourseMode .DEFAULT_MODE_SLUG
2297+ assert entry ['is_active' ] is True
2298+
2299+ def test_enrollment_course_details_fields (self ):
2300+ """course_details nested object contains the expected fields."""
2301+ body = self ._get_enrollments ().json ()
2302+ course_details = body [0 ]['course_details' ]
2303+ for field in ('course_id' , 'course_name' , 'enrollment_start' , 'enrollment_end' ,
2304+ 'course_start' , 'course_end' , 'invite_only' , 'course_modes' ):
2305+ assert field in course_details , f"Missing course_details field: { field } "
2306+
2307+ def test_no_enrollments_returns_empty_list (self ):
2308+ """A user with no enrollments gets an empty list, not null or an error."""
2309+ new_user = UserFactory .create (password = self .PASSWORD )
2310+ self .client .login (username = new_user .username , password = self .PASSWORD )
2311+ body = self .client .get (reverse ('courseenrollments' )).json ()
2312+ assert body == []
2313+
2314+
2315+ @skip_unless_lms
2316+ class UserRoleViewResponseShapeTest (ModuleStoreTestCase ):
2317+ """
2318+ Tests that verify EnrollmentUserRolesView (GET /enrollment/v1/roles/)
2319+ response structure is preserved after migrating to UserRolesResponseSerializer (ADR 0025).
2320+ """
2321+
2322+ USERNAME = "RoleTester"
2323+ PASSWORD = "edx"
2324+
2325+ def setUp (self ):
2326+ super ().setUp ()
2327+ self .course = CourseFactory .create (emit_signals = True , org = "testorg" , course = "c1" , run = "r1" )
2328+ self .user = UserFactory .create (username = self .USERNAME , password = self .PASSWORD )
2329+ self .client .login (username = self .USERNAME , password = self .PASSWORD )
2330+
2331+ def _get_roles (self , course_id = None ):
2332+ url = reverse ('roles' )
2333+ if course_id :
2334+ url += f'?course_id={ course_id } '
2335+ return self .client .get (url )
2336+
2337+ def test_returns_200 (self ):
2338+ assert self ._get_roles ().status_code == status .HTTP_200_OK
2339+
2340+ def test_response_top_level_keys (self ):
2341+ """Response always contains 'roles' (list) and 'is_staff' (bool)."""
2342+ body = self ._get_roles ().json ()
2343+ assert 'roles' in body
2344+ assert 'is_staff' in body
2345+ assert isinstance (body ['roles' ], list )
2346+ assert isinstance (body ['is_staff' ], bool )
2347+
2348+ def test_no_roles_returns_empty_list (self ):
2349+ body = self ._get_roles ().json ()
2350+ assert body ['roles' ] == []
2351+ assert body ['is_staff' ] is False
2352+
2353+ def test_role_entry_shape (self ):
2354+ """A role entry contains org, course_id, and role fields."""
2355+ role = CourseStaffRole (self .course .id )
2356+ role .add_users (self .user )
2357+ body = self ._get_roles ().json ()
2358+ assert len (body ['roles' ]) == 1
2359+ entry = body ['roles' ][0 ]
2360+ for field in ('org' , 'course_id' , 'role' ):
2361+ assert field in entry , f"Missing role field: { field } "
2362+ assert entry ['org' ] == self .course .org
2363+ assert entry ['course_id' ] == str (self .course .id )
2364+
2365+ def test_is_staff_true_for_staff_user (self ):
2366+ staff_user = UserFactory .create (password = self .PASSWORD , is_staff = True )
2367+ self .client .login (username = staff_user .username , password = self .PASSWORD )
2368+ body = self ._get_roles ().json ()
2369+ assert body ['is_staff' ] is True
2370+
2371+ def test_filter_by_course_id (self ):
2372+ """course_id query param filters roles to that course only."""
2373+ course2 = CourseFactory .create (emit_signals = True , org = "other" , course = "c2" , run = "r2" )
2374+ CourseStaffRole (self .course .id ).add_users (self .user )
2375+ CourseStaffRole (course2 .id ).add_users (self .user )
2376+ body = self ._get_roles (course_id = str (self .course .id )).json ()
2377+ assert all (r ['course_id' ] == str (self .course .id ) for r in body ['roles' ])
0 commit comments