diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py index f3db944c631c513dfa067989af845a563c144d77..0e2b3e3796426a48d3a9062820d6442b9b1aa540 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py @@ -193,10 +193,10 @@ class RetirementTestCase(TestCase): ('ENROLLMENTS_COMPLETE', 130, False, False), ('RETIRING_NOTES', 140, False, False), ('NOTES_COMPLETE', 150, False, False), - ('NOTIFYING_PARTNERS', 160, False, False), - ('PARTNERS_NOTIFIED', 170, False, False), - ('RETIRING_LMS', 180, False, False), - ('LMS_COMPLETE', 190, False, False), + ('RETIRING_LMS', 160, False, False), + ('LMS_COMPLETE', 170, False, False), + ('ADDING_TO_PARTNER_QUEUE', 180, False, False), + ('PARTNER_QUEUE_COMPLETE', 190, False, False), ('ERRORED', 200, True, True), ('ABORTED', 210, True, True), ('COMPLETE', 220, True, True), @@ -560,6 +560,68 @@ class TestPartnerReportingCleanup(ModuleStoreTestCase): self.assert_status_and_count(statuses, len(statuses)) +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS') +class TestPartnerReportingPut(RetirementTestCase, ModuleStoreTestCase): + """ + Tests the partner reporting list endpoint + """ + + def setUp(self): + super(TestPartnerReportingPut, self).setUp() + self.test_superuser = SuperuserFactory() + self.course = CourseFactory() + self.course_awesome_org = CourseFactory(org='awesome_org') + self.courses = (self.course, self.course_awesome_org) + self.headers = build_jwt_headers(self.test_superuser) + self.url = reverse('accounts_retirement_partner_report') + self.headers['content_type'] = "application/json" + self.maxDiff = None + self.partner_queue_state = RetirementState.objects.get(state_name='ADDING_TO_PARTNER_QUEUE') + + def post_and_assert_status(self, data, expected_status=status.HTTP_204_NO_CONTENT): + """ + Helper function for making a request to the retire subscriptions endpoint, and asserting the status. + """ + response = self.client.put(self.url, json.dumps(data), **self.headers) + self.assertEqual(response.status_code, expected_status) + return response + + def test_success(self): + """ + Checks the simple success case of creating a user, enrolling in a course, and doing the partner + report PUT. User should then have the appropriate row in UserRetirementPartnerReportingStatus + """ + retirement = self._create_retirement(self.partner_queue_state) + for course in self.courses: + CourseEnrollment.enroll(user=retirement.user, course_key=course.id) + + self.post_and_assert_status({'username': retirement.original_username}) + self.assertTrue(UserRetirementPartnerReportingStatus.objects.filter(user=retirement.user).exists()) + + def test_idempotent(self): + """ + Runs the success test twice to make sure that re-running the step still succeeds. + """ + retirement = self._create_retirement(self.partner_queue_state) + for course in self.courses: + CourseEnrollment.enroll(user=retirement.user, course_key=course.id) + + # We really do want this twice. + self.post_and_assert_status({'username': retirement.original_username}) + self.post_and_assert_status({'username': retirement.original_username}) + self.assertTrue(UserRetirementPartnerReportingStatus.objects.filter(user=retirement.user).exists()) + + def test_unknown_user(self): + """ + Checks that a username with no active retirement generates a 404 + """ + user = UserFactory() + for course in self.courses: + CourseEnrollment.enroll(user=user, course_key=course.id) + + self.post_and_assert_status({'username': user.username}, status.HTTP_404_NOT_FOUND) + + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS') class TestPartnerReportingList(ModuleStoreTestCase): """ @@ -624,7 +686,7 @@ class TestPartnerReportingList(ModuleStoreTestCase): def assert_status_and_user_list(self, expected_users, expected_status=status.HTTP_200_OK): """ - Makes the partner reporting list GET and asserts that the given users are + Makes the partner reporting list POST and asserts that the given users are in the returned list, as well as asserting the expected HTTP status code is returned. """ diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index 9338112f98fa296897909308fb849982cfe7f0f6..e74f3eb40f52338162deed679b0506ba1a6a28ef 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -572,6 +572,38 @@ class AccountRetirementPartnerReportView(ViewSet): return Response(serializer.data) + @request_requires_username + def retirement_partner_status_create(self, request): + """ + PUT /api/user/v1/accounts/retirement_partner_report/ + + { + 'username': 'user_to_retire' + } + + Creates a UserRetirementPartnerReportingStatus object for the given user + as part of the retirement pipeline. + """ + username = request.data['username'] + + try: + retirement = UserRetirementStatus.get_retirement_for_retirement_action(username) + orgs = self._get_orgs_for_user(retirement.user) + + if orgs: + UserRetirementPartnerReportingStatus.objects.get_or_create( + user=retirement.user, + defaults={ + 'original_username': retirement.original_username, + 'original_email': retirement.original_email, + 'original_name': retirement.original_name + } + ) + + return Response(status=status.HTTP_204_NO_CONTENT) + except UserRetirementStatus.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + def retirement_partner_cleanup(self, request): """ DELETE /api/user/v1/accounts/retirement_partner_report/ diff --git a/openedx/core/djangoapps/user_api/urls.py b/openedx/core/djangoapps/user_api/urls.py index 33c648a703481e1b7a8587fa76750a55a44b3869..494a7d396168096134d18fa4b6e546aefcec8eba 100644 --- a/openedx/core/djangoapps/user_api/urls.py +++ b/openedx/core/djangoapps/user_api/urls.py @@ -35,6 +35,7 @@ ACCOUNT_DETAIL = AccountViewSet.as_view({ PARTNER_REPORT = AccountRetirementPartnerReportView.as_view({ 'post': 'retirement_partner_report', + 'put': 'retirement_partner_status_create', 'delete': 'retirement_partner_cleanup' })