diff --git a/src/registrar/management/commands/extend_expiration_dates.py b/src/registrar/management/commands/extend_expiration_dates.py index e9bcc2ff4..715313b91 100644 --- a/src/registrar/management/commands/extend_expiration_dates.py +++ b/src/registrar/management/commands/extend_expiration_dates.py @@ -9,6 +9,7 @@ from epplibwrapper.errors import RegistryError from registrar.models import Domain from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper from dateutil.relativedelta import relativedelta +try: from epplib.exceptions import TransportError except ImportError: pass @@ -25,7 +26,6 @@ class Command(BaseCommand): self.update_success = [] self.update_skipped = [] self.update_failed = [] - self.debug = False def add_arguments(self, parser): """Add command line arguments.""" @@ -53,11 +53,27 @@ class Command(BaseCommand): ) def handle(self, **options): - """""" + """ + Extends the expiration dates for valid domains. + + 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. + 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. + + 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. + Otherwise, the expiration date of the domain is extended. + + Finally, it logs a summary of the script run, + including the number of successful, failed, and skipped updates. + """ + + # Retrieve command line options extension_amount = options.get("extensionAmount") limit_parse = options.get("limitParse") disable_idempotence = options.get("disableIdempotentCheck") - self.debug = options.get("debug") + debug = options.get("debug") # Does a check to see if parse_limit is a positive int. # Raise an error if not. @@ -84,11 +100,11 @@ class Command(BaseCommand): if not disable_idempotence and not is_idempotent: self.update_skipped.append(domain.name) else: - self.extend_expiration_date_on_domain(domain, extension_amount) + self.extend_expiration_date_on_domain(domain, extension_amount, debug) - self.log_script_run_summary() + self.log_script_run_summary(debug) - def extend_expiration_date_on_domain(self, domain: Domain, extension_amount: int): + def extend_expiration_date_on_domain(self, domain: Domain, extension_amount: int, debug: bool): """ Given a particular domain, extend the expiration date by the period specified in extension_amount @@ -103,7 +119,7 @@ class Command(BaseCommand): ) logger.error(err) except Exception as err: - self.log_script_run_summary() + self.log_script_run_summary(debug) raise err else: self.update_success.append(domain.name) @@ -123,7 +139,7 @@ class Command(BaseCommand): # 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 # solution would be a DB flag. - is_idempotent = proposed_date < date.today() + relativedelta(years=extension_amount) + is_idempotent = proposed_date < (date.today() + relativedelta(years=extension_amount+1)) return is_idempotent def prompt_user_to_proceed(self, extension_amount, domains_to_change_count): @@ -146,7 +162,20 @@ class Command(BaseCommand): f"{TerminalColors.ENDC}" ) - def log_script_run_summary(self): + def check_if_positive_int(self, value: int, var_name: str): + """ + Determines if the given integer value is positive or not. + If not, it raises an ArgumentTypeError + """ + if value < 0: + raise argparse.ArgumentTypeError( + f"{value} is an invalid integer value for {var_name}. " + "Must be positive." + ) + + return value + + def log_script_run_summary(self, debug): """Prints success, failed, and skipped counts, as well as all affected domains.""" update_success_count = len(self.update_success) @@ -161,7 +190,7 @@ class Command(BaseCommand): """ ) TerminalHelper.print_conditional( - self.debug, + debug, f""" {TerminalColors.OKGREEN} Updated the following Domains: {self.update_success} @@ -170,7 +199,7 @@ class Command(BaseCommand): ) elif update_failed_count == 0: TerminalHelper.print_conditional( - self.debug, + debug, f""" {TerminalColors.OKGREEN} Updated the following Domains: {self.update_success} @@ -193,7 +222,7 @@ class Command(BaseCommand): ) else: TerminalHelper.print_conditional( - self.debug, + debug, f""" {TerminalColors.OKGREEN} Updated the following Domains: {self.update_success} @@ -218,18 +247,4 @@ class Command(BaseCommand): Skipped updating {update_skipped_count} Domain entries {TerminalColors.ENDC} """ - ) - - - def check_if_positive_int(self, value: int, var_name: str): - """ - Determines if the given integer value is positive or not. - If not, it raises an ArgumentTypeError - """ - if value < 0: - raise argparse.ArgumentTypeError( - f"{value} is an invalid integer value for {var_name}. " - "Must be positive." - ) - - return value \ No newline at end of file + ) \ No newline at end of file diff --git a/src/registrar/tests/test_transition_domain_migrations.py b/src/registrar/tests/test_transition_domain_migrations.py index 4e549bdd6..0fd36a2d4 100644 --- a/src/registrar/tests/test_transition_domain_migrations.py +++ b/src/registrar/tests/test_transition_domain_migrations.py @@ -21,6 +21,70 @@ from registrar.models.contact import Contact from .common import less_console_noise +class TestExtendExpirationDates(TestCase): + def setUp(self): + """Defines the file name of migration_json and the folder its contained in""" + self.test_data_file_location = "registrar/tests/data" + self.migration_json_filename = "test_migrationFilepaths.json" + + def tearDown(self): + """Deletes all DB objects related to migrations""" + # Delete domain information + Domain.objects.all().delete() + DomainInformation.objects.all().delete() + DomainInvitation.objects.all().delete() + TransitionDomain.objects.all().delete() + + # Delete users + User.objects.all().delete() + UserDomainRole.objects.all().delete() + + def run_load_domains(self): + """ + This method executes the load_transition_domain command. + + It uses 'unittest.mock.patch' to mock the TerminalHelper.query_yes_no_exit method, + which is a user prompt in the terminal. The mock function always returns True, + allowing the test to proceed without manual user input. + + The 'call_command' function from Django's management framework is then used to + execute the load_transition_domain command with the specified arguments. + """ + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command( + "load_transition_domain", + self.migration_json_filename, + directory=self.test_data_file_location, + ) + + def run_transfer_domains(self): + """ + This method executes the transfer_transition_domains_to_domains command. + + The 'call_command' function from Django's management framework is then used to + execute the load_transition_domain command with the specified arguments. + """ + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command("transfer_transition_domains_to_domains") + + def run_extend_expiration_dates(self): + """ + This method executes the transfer_transition_domains_to_domains command. + + The 'call_command' function from Django's management framework is then used to + execute the load_transition_domain command with the specified arguments. + """ + call_command("extend_expiration_dates") + + def test_extends_correctly(self): + pass + class TestOrganizationMigration(TestCase): def setUp(self): """Defines the file name of migration_json and the folder its contained in"""