From b773ec1c5e08aae3a9796e6ab0552e81d2239b44 Mon Sep 17 00:00:00 2001 From: Alex Dusenbery Date: Fri, 5 Jan 2024 12:00:19 -0500 Subject: [PATCH] fix: account for license source records during license retirement ENT-8172 | Delete related ``SubscriptionLicenseSource`` records when licenses are retired or expired. --- .gitignore | 1 + license_manager/apps/api/v1/tests/test_views.py | 13 ++++++++++++- license_manager/apps/api/v1/views.py | 1 + .../management/commands/retire_old_licenses.py | 8 ++++++-- .../commands/tests/test_retire_old_licenses.py | 13 ++++++++++++- license_manager/apps/subscriptions/models.py | 9 +++++++++ 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index b29675a4..e2ac8bd9 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ license_manager/conf/locale/messages.mo # emacs *~ +.projectile # QA coverage.xml diff --git a/license_manager/apps/api/v1/tests/test_views.py b/license_manager/apps/api/v1/tests/test_views.py index 7bae848d..d1150d0d 100644 --- a/license_manager/apps/api/v1/tests/test_views.py +++ b/license_manager/apps/api/v1/tests/test_views.py @@ -48,6 +48,7 @@ from license_manager.apps.subscriptions.tests.factories import ( CustomerAgreementFactory, LicenseFactory, + SubscriptionLicenseSourceFactory, SubscriptionPlanFactory, SubscriptionPlanRenewalFactory, UserFactory, @@ -3341,11 +3342,13 @@ def _create_associated_license(cls, status): """ Helper to create a license of the given status associated with the user being retired. """ - return LicenseFactory.create( + _license = LicenseFactory.create( status=status, lms_user_id=cls.lms_user_id, user_email=cls.user_email, ) + SubscriptionLicenseSourceFactory.create(license=_license) + return _license def _post_request(self, lms_user_id, original_username): """ @@ -3423,6 +3426,9 @@ def test_retirement(self): """ All licenses associated with the user being retired should have pii scrubbed, and the user should be deleted. """ + for _license in (self.revoked_license, self.assigned_license, self.activated_license): + assert _license.source + # Verify the request succeeds with the correct status and logs the appropriate messages with self.assertLogs(level='INFO') as log: response = self._post_request(self.lms_user_id, self.original_username) @@ -3464,6 +3470,11 @@ def test_retirement(self): with self.assertRaises(ObjectDoesNotExist): User.objects.get(username=self.original_username) + # Verify license source records are deleted + for _license in (self.revoked_license, self.assigned_license, self.activated_license): + with self.assertRaises(SubscriptionLicenseSource.DoesNotExist): + _license.source # pylint: disable=pointless-statement + class StaffLicenseLookupViewTests(LicenseViewTestMixin, TestCase): """ diff --git a/license_manager/apps/api/v1/views.py b/license_manager/apps/api/v1/views.py index d1b34b65..0a2ddc6d 100644 --- a/license_manager/apps/api/v1/views.py +++ b/license_manager/apps/api/v1/views.py @@ -1531,6 +1531,7 @@ def post(self, request): associated_license.save() # Clear historical pii after removing pii from the license itself associated_license.clear_historical_pii() + associated_license.delete_source() associated_licenses_uuids = [license.uuid for license in associated_licenses] message = 'Retired {} licenses with uuids: {} for user with lms_user_id {}'.format( len(associated_licenses_uuids), diff --git a/license_manager/apps/subscriptions/management/commands/retire_old_licenses.py b/license_manager/apps/subscriptions/management/commands/retire_old_licenses.py index f54bea1d..f4bb24f7 100644 --- a/license_manager/apps/subscriptions/management/commands/retire_old_licenses.py +++ b/license_manager/apps/subscriptions/management/commands/retire_old_licenses.py @@ -27,8 +27,6 @@ class Command(BaseCommand): ) def handle(self, *args, **options): - ready_for_retirement_date = localized_utcnow() - timedelta(DAYS_TO_RETIRE) - expired_licenses_for_retirement = License.get_licenses_exceeding_purge_duration( 'subscription_plan__expiration_date', ) @@ -50,6 +48,8 @@ def handle(self, *args, **options): # Clear historical pii after removing pii from the license itself expired_license.clear_historical_pii() + expired_license.delete_source() + expired_license_uuids = sorted([expired_license.uuid for expired_license in expired_licenses_for_retirement]) message = 'Retired {} expired licenses with uuids: {}'.format(len(expired_license_uuids), expired_license_uuids) logger.info(message) @@ -65,6 +65,8 @@ def handle(self, *args, **options): revoked_license.save() # Clear historical pii after removing pii from the license itself revoked_license.clear_historical_pii() + revoked_license.delete_source() + revoked_license_uuids = sorted([revoked_license.uuid for revoked_license in revoked_licenses_for_retirement]) message = 'Retired {} revoked licenses with uuids: {}'.format(len(revoked_license_uuids), revoked_license_uuids) logger.info(message) @@ -82,6 +84,8 @@ def handle(self, *args, **options): assigned_license.save() # Clear historical pii after removing pii from the license itself assigned_license.clear_historical_pii() + assigned_license.delete_source() + assigned_license_uuids = sorted( [assigned_license.uuid for assigned_license in assigned_licenses_for_retirement], ) diff --git a/license_manager/apps/subscriptions/management/commands/tests/test_retire_old_licenses.py b/license_manager/apps/subscriptions/management/commands/tests/test_retire_old_licenses.py index 45d790b3..e715e389 100644 --- a/license_manager/apps/subscriptions/management/commands/tests/test_retire_old_licenses.py +++ b/license_manager/apps/subscriptions/management/commands/tests/test_retire_old_licenses.py @@ -13,9 +13,13 @@ REVOKED, UNASSIGNED, ) -from license_manager.apps.subscriptions.models import License +from license_manager.apps.subscriptions.models import ( + License, + SubscriptionLicenseSource, +) from license_manager.apps.subscriptions.tests.factories import ( LicenseFactory, + SubscriptionLicenseSourceFactory, SubscriptionPlanFactory, ) from license_manager.apps.subscriptions.tests.utils import ( @@ -88,6 +92,7 @@ def setUpTestData(cls): revoked_license.lms_user_id = faker.random_int() revoked_license.user_email = faker.email() revoked_license.save() + SubscriptionLicenseSourceFactory.create(license=revoked_license) cls.num_assigned_licenses_to_retire = 7 cls.assigned_licenses_ready_for_retirement = LicenseFactory.create_batch( @@ -100,6 +105,7 @@ def setUpTestData(cls): assigned_license.lms_user_id = faker.random_int() assigned_license.user_email = faker.email() assigned_license.save() + SubscriptionLicenseSourceFactory.create(license=assigned_license) # Create licenses of different statuses that should be retired from association with an old expired subscription LicenseFactory.create( @@ -145,6 +151,7 @@ def test_retire_old_licenses(self, _): assert_pii_cleared(expired_license) assert expired_license.status == REVOKED assert_historical_pii_cleared(expired_license) + message = 'Retired {} expired licenses with uuids: {}'.format( expired_licenses.count(), sorted([expired_license.uuid for expired_license in expired_licenses]), @@ -156,6 +163,9 @@ def test_retire_old_licenses(self, _): revoked_license.refresh_from_db() assert_pii_cleared(revoked_license) assert_historical_pii_cleared(revoked_license) + with self.assertRaises(SubscriptionLicenseSource.DoesNotExist): + revoked_license.source + message = 'Retired {} revoked licenses with uuids: {}'.format( self.num_revoked_licenses_to_retire, sorted([revoked_license.uuid for revoked_license in self.revoked_licenses_ready_for_retirement]), @@ -170,6 +180,7 @@ def test_retire_old_licenses(self, _): assert_historical_pii_cleared(assigned_license) assert assigned_license.activation_key is None assert assigned_license.status == UNASSIGNED + message = 'Retired {} assigned licenses that exceeded their inactivation duration with uuids: {}'.format( self.num_assigned_licenses_to_retire, sorted([assigned_license.uuid for assigned_license in self.assigned_licenses_ready_for_retirement]), diff --git a/license_manager/apps/subscriptions/models.py b/license_manager/apps/subscriptions/models.py index 8469d484..2d21ffb7 100644 --- a/license_manager/apps/subscriptions/models.py +++ b/license_manager/apps/subscriptions/models.py @@ -1081,6 +1081,15 @@ def revoke(self): SegmentEvents.LICENSE_REVOKED, event_properties) + def delete_source(self): + """ + Deletes any related ``SubscriptionLicenseSource`` record. + """ + try: + self.source.delete() # pylint: disable=no-member + except SubscriptionLicenseSource.DoesNotExist: + logger.warning('Could not find related license source to delete for license %s', self.uuid) + def activate(self, lms_user_id): """ Update this license to activated and set the lms_user_id.