Skip to content

Commit

Permalink
fix: account for license source records during license retirement
Browse files Browse the repository at this point in the history
ENT-8172 | Delete related ``SubscriptionLicenseSource`` records when licenses are retired or expired.
  • Loading branch information
iloveagent57 committed Jan 8, 2024
1 parent 8ea5ecd commit b773ec1
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ license_manager/conf/locale/messages.mo

# emacs
*~
.projectile

# QA
coverage.xml
Expand Down
13 changes: 12 additions & 1 deletion license_manager/apps/api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from license_manager.apps.subscriptions.tests.factories import (
CustomerAgreementFactory,
LicenseFactory,
SubscriptionLicenseSourceFactory,
SubscriptionPlanFactory,
SubscriptionPlanRenewalFactory,
UserFactory,
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
"""
Expand Down
1 change: 1 addition & 0 deletions license_manager/apps/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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]),
Expand All @@ -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]),
Expand All @@ -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]),
Expand Down
9 changes: 9 additions & 0 deletions license_manager/apps/subscriptions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit b773ec1

Please sign in to comment.