Add unit tests

This commit is contained in:
zandercymatics 2023-12-08 11:19:11 -07:00
parent 93b5cee23d
commit ae6cea7100
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
3 changed files with 105 additions and 56 deletions

View file

@ -9,6 +9,7 @@ from epplibwrapper.errors import RegistryError
from registrar.models import Domain from registrar.models import Domain
from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
try: try:
from epplib.exceptions import TransportError from epplib.exceptions import TransportError
except ImportError: except ImportError:
@ -42,15 +43,9 @@ class Command(BaseCommand):
help="Sets a cap on the number of records to parse", help="Sets a cap on the number of records to parse",
) )
parser.add_argument( parser.add_argument(
"--disableIdempotentCheck", "--disableIdempotentCheck", action=argparse.BooleanOptionalAction, help="Disable script idempotence"
action=argparse.BooleanOptionalAction,
help="Disable script idempotence"
)
parser.add_argument(
"--debug",
action=argparse.BooleanOptionalAction,
help="Increases log chattiness"
) )
parser.add_argument("--debug", action=argparse.BooleanOptionalAction, help="Increases log chattiness")
def handle(self, **options): def handle(self, **options):
""" """
@ -58,14 +53,14 @@ class Command(BaseCommand):
It first retrieves the command line options and checks if the parse limit is a positive integer. It first retrieves the command line options and checks if the parse limit is a positive integer.
Then, it fetches the valid domains from the database and calculates the number of domains to change. Then, it fetches the valid domains from the database and calculates the number of domains to change.
If a parse limit is set and it's less than the total number of valid domains, If a parse limit is set and it's less than the total number of valid domains,
the number of domains to change is set to the parse limit. the number of domains to change is set to the parse limit.
For each domain, it checks if the operation is idempotent. For each domain, it checks if the operation is idempotent.
If the idempotence check is not disabled and the operation is not idempotent, the domain is skipped. If the idempotence check is not disabled and the operation is not idempotent, the domain is skipped.
Otherwise, the expiration date of the domain is extended. Otherwise, the expiration date of the domain is extended.
Finally, it logs a summary of the script run, Finally, it logs a summary of the script run,
including the number of successful, failed, and skipped updates. including the number of successful, failed, and skipped updates.
""" """
@ -80,8 +75,7 @@ class Command(BaseCommand):
self.check_if_positive_int(limit_parse, "limitParse") self.check_if_positive_int(limit_parse, "limitParse")
valid_domains = Domain.objects.filter( valid_domains = Domain.objects.filter(
expiration_date__gte=date(2023, 11, 15), expiration_date__gte=date(2023, 11, 15), state=Domain.State.READY
state=Domain.State.READY
).order_by("name") ).order_by("name")
domains_to_change_count = valid_domains.count() domains_to_change_count = valid_domains.count()
@ -95,15 +89,15 @@ class Command(BaseCommand):
for i, domain in enumerate(valid_domains): for i, domain in enumerate(valid_domains):
if limit_parse != 0 and i > limit_parse: if limit_parse != 0 and i > limit_parse:
break break
is_idempotent = self.idempotence_check(domain, extension_amount) is_idempotent = self.idempotence_check(domain, extension_amount)
if not disable_idempotence and not is_idempotent: if not disable_idempotence and not is_idempotent:
self.update_skipped.append(domain.name) self.update_skipped.append(domain.name)
else: else:
self.extend_expiration_date_on_domain(domain, extension_amount, debug) self.extend_expiration_date_on_domain(domain, extension_amount, debug)
self.log_script_run_summary(debug) self.log_script_run_summary(debug)
def extend_expiration_date_on_domain(self, domain: Domain, extension_amount: int, debug: bool): def extend_expiration_date_on_domain(self, domain: Domain, extension_amount: int, debug: bool):
""" """
Given a particular domain, Given a particular domain,
@ -113,9 +107,7 @@ class Command(BaseCommand):
domain.renew_domain(extension_amount) domain.renew_domain(extension_amount)
except (RegistryError, TransportError) as err: except (RegistryError, TransportError) as err:
logger.error( logger.error(
f"{TerminalColors.FAIL}" f"{TerminalColors.FAIL}" f"Failed to update expiration date for {domain}" f"{TerminalColors.ENDC}"
f"Failed to update expiration date for {domain}"
f"{TerminalColors.ENDC}"
) )
logger.error(err) logger.error(err)
except Exception as err: except Exception as err:
@ -124,9 +116,7 @@ class Command(BaseCommand):
else: else:
self.update_success.append(domain.name) self.update_success.append(domain.name)
logger.info( logger.info(
f"{TerminalColors.OKCYAN}" f"{TerminalColors.OKCYAN}" f"Successfully updated expiration date for {domain}" f"{TerminalColors.ENDC}"
f"Successfully updated expiration date for {domain}"
f"{TerminalColors.ENDC}"
) )
# == Helper functions == # # == Helper functions == #
@ -139,7 +129,7 @@ class Command(BaseCommand):
# CAVEAT: This check stops working after a year has elapsed between when this script # CAVEAT: This check stops working after a year has elapsed between when this script
# was ran, and when it was ran again. This is good enough for now, but a more robust # was ran, and when it was ran again. This is good enough for now, but a more robust
# solution would be a DB flag. # solution would be a DB flag.
is_idempotent = proposed_date < (date.today() + relativedelta(years=extension_amount+1)) is_idempotent = proposed_date < (date.today() + relativedelta(years=extension_amount + 1))
return is_idempotent return is_idempotent
def prompt_user_to_proceed(self, extension_amount, domains_to_change_count): def prompt_user_to_proceed(self, extension_amount, domains_to_change_count):
@ -156,27 +146,22 @@ class Command(BaseCommand):
prompt_title="Do you wish to modify Expiration Dates for the given Domains?", prompt_title="Do you wish to modify Expiration Dates for the given Domains?",
) )
logger.info( logger.info(f"{TerminalColors.MAGENTA}" "Preparing to extend expiration dates..." f"{TerminalColors.ENDC}")
f"{TerminalColors.MAGENTA}"
"Preparing to extend expiration dates..."
f"{TerminalColors.ENDC}"
)
def check_if_positive_int(self, value: int, var_name: str): def check_if_positive_int(self, value: int, var_name: str):
""" """
Determines if the given integer value is positive or not. Determines if the given integer value is positive or not.
If not, it raises an ArgumentTypeError If not, it raises an ArgumentTypeError
""" """
if value < 0: if value < 0:
raise argparse.ArgumentTypeError( raise argparse.ArgumentTypeError(
f"{value} is an invalid integer value for {var_name}. " f"{value} is an invalid integer value for {var_name}. " "Must be positive."
"Must be positive."
) )
return value return value
def log_script_run_summary(self, debug): def log_script_run_summary(self, debug):
"""Prints success, failed, and skipped counts, as well as """Prints success, failed, and skipped counts, as well as
all affected domains.""" all affected domains."""
update_success_count = len(self.update_success) update_success_count = len(self.update_success)
update_failed_count = len(self.update_failed) update_failed_count = len(self.update_failed)
@ -247,4 +232,4 @@ class Command(BaseCommand):
Skipped updating {update_skipped_count} Domain entries Skipped updating {update_skipped_count} Domain entries
{TerminalColors.ENDC} {TerminalColors.ENDC}
""" """
) )

View file

@ -819,6 +819,16 @@ class MockEppLib(TestCase):
ex_date=datetime.date(2023, 5, 25), ex_date=datetime.date(2023, 5, 25),
) )
mockDnsNeededRenewedDomainExpDate = fakedEppObject(
"fakeneeded.gov",
ex_date=datetime.date(2023, 2, 15),
)
mockRecentRenewedDomainExpDate = fakedEppObject(
"waterbutpurple.gov",
ex_date=datetime.date(2025, 1, 10),
)
def _mockDomainName(self, _name, _avail=False): def _mockDomainName(self, _name, _avail=False):
return MagicMock( return MagicMock(
res_data=[ res_data=[
@ -919,6 +929,16 @@ class MockEppLib(TestCase):
def mockRenewDomainCommand(self, _request, cleaned): def mockRenewDomainCommand(self, _request, cleaned):
if getattr(_request, "name", None) == "fake-error.gov": if getattr(_request, "name", None) == "fake-error.gov":
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR) raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
elif getattr(_request, "name", None) == "waterbutpurple.gov":
return MagicMock(
res_data=[self.mockRecentRenewedDomainExpDate],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
)
elif getattr(_request, "name", None) == "fakeneeded.gov":
return MagicMock(
res_data=[self.mockDnsNeededRenewedDomainExpDate],
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
)
else: else:
return MagicMock( return MagicMock(
res_data=[self.mockRenewedDomainExpDate], res_data=[self.mockRenewedDomainExpDate],

View file

@ -26,10 +26,14 @@ class TestExtendExpirationDates(MockEppLib):
def setUp(self): def setUp(self):
"""Defines the file name of migration_json and the folder its contained in""" """Defines the file name of migration_json and the folder its contained in"""
super().setUp() super().setUp()
self.domain, _ = Domain.objects.get_or_create( Domain.objects.get_or_create(
name="fake.gov", name="waterbutpurple.gov", state=Domain.State.READY, expiration_date=datetime.date(2023, 11, 15)
state=Domain.State.READY, )
expiration_date=datetime.date(2023, 5, 25) Domain.objects.get_or_create(
name="fake.gov", state=Domain.State.READY, expiration_date=datetime.date(2022, 5, 25)
)
Domain.objects.get_or_create(
name="fakeneeded.gov", state=Domain.State.DNS_NEEDED, expiration_date=datetime.date(2023, 11, 15)
) )
def tearDown(self): def tearDown(self):
@ -44,7 +48,7 @@ class TestExtendExpirationDates(MockEppLib):
# Delete users # Delete users
User.objects.all().delete() User.objects.all().delete()
UserDomainRole.objects.all().delete() UserDomainRole.objects.all().delete()
def run_extend_expiration_dates(self): def run_extend_expiration_dates(self):
""" """
This method executes the transfer_transition_domains_to_domains command. This method executes the transfer_transition_domains_to_domains command.
@ -57,47 +61,87 @@ class TestExtendExpirationDates(MockEppLib):
return_value=True, return_value=True,
): ):
call_command("extend_expiration_dates") call_command("extend_expiration_dates")
def test_extends_expiration_date_correctly(self): def test_extends_expiration_date_correctly(self):
desired_domain = Domain.objects.filter(name="fake.gov").get() """
Tests that the extend_expiration_dates method extends dates as expected
"""
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1) desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1)
# Run the expiration date script # Run the expiration date script
self.run_extend_expiration_dates() self.run_extend_expiration_dates()
self.assertEqual(desired_domain, self.domain)
# Explicitly test the expiration date current_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
self.assertEqual(self.domain.expiration_date, datetime.date(2024, 5, 25)) self.assertEqual(desired_domain, current_domain)
# TODO ALSO NEED A TEST FOR NON READY DOMAINS # Explicitly test the expiration date
self.assertEqual(current_domain.expiration_date, datetime.date(2025, 1, 10))
def test_extends_expiration_date_skips_non_current(self): def test_extends_expiration_date_skips_non_current(self):
"""
Tests that the extend_expiration_dates method correctly skips domains
with an expiration date less than a certain threshold.
"""
desired_domain = Domain.objects.filter(name="fake.gov").get() desired_domain = Domain.objects.filter(name="fake.gov").get()
desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1) desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1)
# Run the expiration date script # Run the expiration date script
self.run_extend_expiration_dates() self.run_extend_expiration_dates()
current_domain = Domain.objects.filter(name="FakeWebsite3.gov").get() current_domain = Domain.objects.filter(name="fake.gov").get()
self.assertEqual(desired_domain, current_domain) self.assertEqual(desired_domain, current_domain)
# Explicitly test the expiration date. The extend_expiration_dates script # Explicitly test the expiration date. The extend_expiration_dates script
# will skip all dates less than date(2023, 11, 15), meaning that this domain # will skip all dates less than date(2023, 11, 15), meaning that this domain
# should not be affected by the change. # should not be affected by the change.
self.assertEqual(current_domain.expiration_date, datetime.date(2023, 5, 25)) self.assertEqual(current_domain.expiration_date, datetime.date(2022, 5, 25))
def test_extends_expiration_date_idempotent(self): def test_extends_expiration_date_skips_non_ready(self):
desired_domain = Domain.objects.filter(name="FakeWebsite3.gov").get() """
Tests that the extend_expiration_dates method correctly skips domains not in the state "ready"
"""
desired_domain = Domain.objects.filter(name="fakeneeded.gov").get()
desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1) desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1)
# Run the expiration date script # Run the expiration date script
self.run_extend_expiration_dates() self.run_extend_expiration_dates()
current_domain = Domain.objects.filter(name="FakeWebsite3.gov").get() current_domain = Domain.objects.filter(name="fake.gov").get()
self.assertEqual(desired_domain, current_domain) self.assertEqual(desired_domain, current_domain)
# Explicitly test the expiration date # Explicitly test the expiration date. The extend_expiration_dates script
self.assertEqual(desired_domain.expiration_date, datetime.date(2023, 9, 30)) # will skip all dates less than date(2023, 11, 15), meaning that this domain
# should not be affected by the change.
self.assertEqual(current_domain.expiration_date, datetime.date(2023, 11, 15))
def test_extends_expiration_date_idempotent(self):
"""
Tests the idempotency of the extend_expiration_dates command.
Verifies that running the method multiple times does not change the expiration date
of a domain beyond the initial extension.
"""
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
desired_domain.expiration_date = desired_domain.expiration_date + relativedelta(years=1)
# Run the expiration date script
self.run_extend_expiration_dates()
current_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
self.assertEqual(desired_domain, current_domain)
# Explicitly test the expiration date
self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15))
# Run the expiration date script again
self.run_extend_expiration_dates()
# The old domain shouldn't have changed
self.assertEqual(desired_domain, current_domain)
# Explicitly test the expiration date - should be the same
self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15))
class TestOrganizationMigration(TestCase): class TestOrganizationMigration(TestCase):