Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: account for license source records during license retirement #554

Merged
merged 1 commit into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading