mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-06 11:13:21 +02:00
Merge branch 'main' into dk/1571-contact-patches
This commit is contained in:
commit
2bc0b28518
36 changed files with 1225 additions and 496 deletions
28
src/.pa11yci
28
src/.pa11yci
|
@ -6,19 +6,19 @@
|
|||
"urls": [
|
||||
"http://localhost:8080/",
|
||||
"http://localhost:8080/health/",
|
||||
"http://localhost:8080/register/",
|
||||
"http://localhost:8080/register/organization/",
|
||||
"http://localhost:8080/register/org_federal/",
|
||||
"http://localhost:8080/register/org_election/",
|
||||
"http://localhost:8080/register/org_contact/",
|
||||
"http://localhost:8080/register/authorizing_official/",
|
||||
"http://localhost:8080/register/current_sites/",
|
||||
"http://localhost:8080/register/dotgov_domain/",
|
||||
"http://localhost:8080/register/purpose/",
|
||||
"http://localhost:8080/register/your_contact/",
|
||||
"http://localhost:8080/register/other_contacts/",
|
||||
"http://localhost:8080/register/anything_else/",
|
||||
"http://localhost:8080/register/requirements/",
|
||||
"http://localhost:8080/register/finished/"
|
||||
"http://localhost:8080/request/",
|
||||
"http://localhost:8080/request/organization/",
|
||||
"http://localhost:8080/request/org_federal/",
|
||||
"http://localhost:8080/request/org_election/",
|
||||
"http://localhost:8080/request/org_contact/",
|
||||
"http://localhost:8080/request/authorizing_official/",
|
||||
"http://localhost:8080/request/current_sites/",
|
||||
"http://localhost:8080/request/dotgov_domain/",
|
||||
"http://localhost:8080/request/purpose/",
|
||||
"http://localhost:8080/request/your_contact/",
|
||||
"http://localhost:8080/request/other_contacts/",
|
||||
"http://localhost:8080/request/anything_else/",
|
||||
"http://localhost:8080/request/requirements/",
|
||||
"http://localhost:8080/request/finished/"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -358,14 +358,12 @@ function markForm(e, formLabel){
|
|||
*/
|
||||
function prepareNewDeleteButton(btn, formLabel) {
|
||||
let formIdentifier = "form"
|
||||
let isNameserversForm = document.title.includes("DNS name servers |");
|
||||
let isOtherContactsForm = document.title.includes("Other employees from your organization");
|
||||
let isNameserversForm = document.querySelector(".nameservers-form");
|
||||
let isOtherContactsForm = document.querySelector(".other-contacts-form");
|
||||
let addButton = document.querySelector("#add-form");
|
||||
|
||||
if (isOtherContactsForm) {
|
||||
formIdentifier = "other_contacts";
|
||||
}
|
||||
|
||||
if (isOtherContactsForm) {
|
||||
// We will mark the forms for deletion
|
||||
btn.addEventListener('click', function(e) {
|
||||
markForm(e, formLabel);
|
||||
|
@ -386,8 +384,8 @@ function prepareNewDeleteButton(btn, formLabel) {
|
|||
function prepareDeleteButtons(formLabel) {
|
||||
let formIdentifier = "form"
|
||||
let deleteButtons = document.querySelectorAll(".delete-record");
|
||||
let isNameserversForm = document.title.includes("DNS name servers |");
|
||||
let isOtherContactsForm = document.title.includes("Other employees from your organization");
|
||||
let isNameserversForm = document.querySelector(".nameservers-form");
|
||||
let isOtherContactsForm = document.querySelector(".other-contacts-form");
|
||||
let addButton = document.querySelector("#add-form");
|
||||
if (isOtherContactsForm) {
|
||||
formIdentifier = "other_contacts";
|
||||
|
@ -443,15 +441,16 @@ function hideDeletedForms() {
|
|||
let addButton = document.querySelector("#add-form");
|
||||
let cloneIndex = 0;
|
||||
let formLabel = '';
|
||||
let isNameserversForm = document.title.includes("DNS name servers |");
|
||||
let isOtherContactsForm = document.title.includes("Other employees from your organization");
|
||||
let isNameserversForm = document.querySelector(".nameservers-form");
|
||||
let isOtherContactsForm = document.querySelector(".other-contacts-form");
|
||||
let isDsDataForm = document.querySelector(".ds-data-form");
|
||||
// The Nameservers formset features 2 required and 11 optionals
|
||||
if (isNameserversForm) {
|
||||
cloneIndex = 2;
|
||||
formLabel = "Name server";
|
||||
// DNSSEC: DS Data
|
||||
} else if (document.title.includes("DS Data |")) {
|
||||
formLabel = "DS Data record";
|
||||
} else if (isDsDataForm) {
|
||||
formLabel = "DS data record";
|
||||
// The Other Contacts form
|
||||
} else if (isOtherContactsForm) {
|
||||
formLabel = "Organization contact";
|
||||
|
|
|
@ -76,7 +76,7 @@ urlpatterns = [
|
|||
),
|
||||
path("health/", views.health),
|
||||
path("openid/", include("djangooidc.urls")),
|
||||
path("register/", include((application_urls, APPLICATION_NAMESPACE))),
|
||||
path("request/", include((application_urls, APPLICATION_NAMESPACE))),
|
||||
path("api/v1/available/", available, name="available"),
|
||||
path("api/v1/get-report/current-federal", get_current_federal, name="get-current-federal"),
|
||||
path("api/v1/get-report/current-full", get_current_full, name="get-current-full"),
|
||||
|
|
|
@ -190,7 +190,7 @@ class TribalGovernmentForm(RegistrarForm):
|
|||
)
|
||||
|
||||
tribe_name = forms.CharField(
|
||||
label="What is the name of the tribe you represent?",
|
||||
label="Name of tribe",
|
||||
error_messages={"required": "Enter the tribe you represent."},
|
||||
)
|
||||
|
||||
|
@ -596,9 +596,12 @@ class OtherContactsYesNoForm(RegistrarForm):
|
|||
|
||||
self.fields["has_other_contacts"] = forms.TypedChoiceField(
|
||||
coerce=lambda x: x.lower() == "true" if x is not None else None, # coerce strings to bool, excepting None
|
||||
choices=((True, "Yes, I can name other employees."), (False, "No (We’ll ask you to explain why).")),
|
||||
choices=((True, "Yes, I can name other employees."), (False, "No. (We’ll ask you to explain why.)")),
|
||||
initial=initial_value,
|
||||
widget=forms.RadioSelect,
|
||||
error_messages={
|
||||
"required": "This question is required.",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
|
262
src/registrar/management/commands/patch_federal_agency_info.py
Normal file
262
src/registrar/management/commands/patch_federal_agency_info.py
Normal file
|
@ -0,0 +1,262 @@
|
|||
"""Loops through each valid DomainInformation object and updates its agency value"""
|
||||
import argparse
|
||||
import csv
|
||||
import logging
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper
|
||||
from registrar.models.domain_information import DomainInformation
|
||||
from django.db.models import Q
|
||||
|
||||
from registrar.models.transition_domain import TransitionDomain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Loops through each valid DomainInformation object and updates its agency value"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.di_to_update: List[DomainInformation] = []
|
||||
self.di_failed_to_update: List[DomainInformation] = []
|
||||
self.di_skipped: List[DomainInformation] = []
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Adds command line arguments"""
|
||||
parser.add_argument(
|
||||
"current_full_filepath",
|
||||
help="TBD",
|
||||
)
|
||||
parser.add_argument("--debug", action=argparse.BooleanOptionalAction)
|
||||
parser.add_argument("--sep", default=",", help="Delimiter character")
|
||||
|
||||
def handle(self, current_full_filepath, **kwargs):
|
||||
"""Loops through each valid DomainInformation object and updates its agency value"""
|
||||
debug = kwargs.get("debug")
|
||||
separator = kwargs.get("sep")
|
||||
|
||||
# Check if the provided file path is valid
|
||||
if not os.path.isfile(current_full_filepath):
|
||||
raise argparse.ArgumentTypeError(f"Invalid file path '{current_full_filepath}'")
|
||||
|
||||
# === Update the "federal_agency" field === #
|
||||
was_success = self.patch_agency_info(debug)
|
||||
|
||||
# === Try to process anything that was skipped === #
|
||||
# We should only correct skipped records if the previous step was successful.
|
||||
# If something goes wrong, then we risk corrupting data, so skip this step.
|
||||
if len(self.di_skipped) > 0 and was_success:
|
||||
# Flush out the list of DomainInformations to update
|
||||
self.di_to_update.clear()
|
||||
self.process_skipped_records(current_full_filepath, separator, debug)
|
||||
|
||||
# Clear the old skipped list, and log the run summary
|
||||
self.di_skipped.clear()
|
||||
self.log_script_run_summary(debug)
|
||||
elif not was_success:
|
||||
# This code should never execute. This can only occur if bulk_update somehow fails,
|
||||
# which may indicate some sort of data corruption.
|
||||
logger.error(
|
||||
f"{TerminalColors.FAIL}"
|
||||
"Could not automatically patch skipped records. The initial update failed."
|
||||
"An error was encountered when running this script, please inspect the following "
|
||||
f"records for accuracy and completeness: {self.di_failed_to_update}"
|
||||
f"{TerminalColors.ENDC}"
|
||||
)
|
||||
|
||||
def patch_agency_info(self, debug):
|
||||
"""
|
||||
Updates the federal_agency field of each valid DomainInformation object based on the corresponding
|
||||
TransitionDomain object. Skips the update if the TransitionDomain object does not exist or its
|
||||
federal_agency field is None. Logs the update, skip, and failure actions if debug mode is on.
|
||||
After all updates, logs a summary of the results.
|
||||
"""
|
||||
|
||||
# Grab all DomainInformation objects (and their associated TransitionDomains)
|
||||
# that need to be updated
|
||||
empty_agency_query = Q(federal_agency=None) | Q(federal_agency="")
|
||||
domain_info_to_fix = DomainInformation.objects.filter(empty_agency_query)
|
||||
|
||||
domain_names = domain_info_to_fix.values_list("domain__name", flat=True)
|
||||
transition_domains = TransitionDomain.objects.filter(domain_name__in=domain_names).exclude(empty_agency_query)
|
||||
|
||||
# Get the domain names from TransitionDomain
|
||||
td_agencies = transition_domains.values_list("domain_name", "federal_agency").distinct()
|
||||
|
||||
human_readable_domain_names = list(domain_names)
|
||||
# Code execution will stop here if the user prompts "N"
|
||||
TerminalHelper.prompt_for_execution(
|
||||
system_exit_on_terminate=True,
|
||||
info_to_inspect=f"""
|
||||
==Proposed Changes==
|
||||
Number of DomainInformation objects to change: {len(human_readable_domain_names)}
|
||||
The following DomainInformation objects will be modified: {human_readable_domain_names}
|
||||
""",
|
||||
prompt_title="Do you wish to patch federal_agency data?",
|
||||
)
|
||||
logger.info("Updating...")
|
||||
|
||||
# Create a dictionary mapping of domain_name to federal_agency
|
||||
td_dict = dict(td_agencies)
|
||||
|
||||
for di in domain_info_to_fix:
|
||||
domain_name = di.domain.name
|
||||
federal_agency = td_dict.get(domain_name)
|
||||
log_message = None
|
||||
|
||||
# If agency exists on a TransitionDomain, update the related DomainInformation object
|
||||
if domain_name in td_dict:
|
||||
di.federal_agency = federal_agency
|
||||
self.di_to_update.append(di)
|
||||
log_message = f"{TerminalColors.OKCYAN}Updated {di}{TerminalColors.ENDC}"
|
||||
else:
|
||||
self.di_skipped.append(di)
|
||||
log_message = f"{TerminalColors.YELLOW}Skipping update for {di}{TerminalColors.ENDC}"
|
||||
|
||||
# Log the action if debug mode is on
|
||||
if debug and log_message is not None:
|
||||
logger.info(log_message)
|
||||
|
||||
# Bulk update the federal agency field in DomainInformation objects
|
||||
DomainInformation.objects.bulk_update(self.di_to_update, ["federal_agency"])
|
||||
|
||||
# Get a list of each domain we changed
|
||||
corrected_domains = DomainInformation.objects.filter(domain__name__in=domain_names)
|
||||
|
||||
# After the update has happened, do a sweep of what we get back.
|
||||
# If the fields we expect to update are still None, then something is wrong.
|
||||
for di in corrected_domains:
|
||||
if di not in self.di_skipped and di.federal_agency is None:
|
||||
logger.info(f"{TerminalColors.FAIL}Failed to update {di}{TerminalColors.ENDC}")
|
||||
self.di_failed_to_update.append(di)
|
||||
|
||||
# === Log results and return data === #
|
||||
self.log_script_run_summary(debug)
|
||||
# Tracks if this script was successful. If any errors are found, something went very wrong.
|
||||
was_success = len(self.di_failed_to_update) == 0
|
||||
return was_success
|
||||
|
||||
def process_skipped_records(self, file_path, separator, debug):
|
||||
"""If we encounter any DomainInformation records that do not have data in the associated
|
||||
TransitionDomain record, then check the associated current-full.csv file for this
|
||||
information."""
|
||||
|
||||
# Code execution will stop here if the user prompts "N"
|
||||
TerminalHelper.prompt_for_execution(
|
||||
system_exit_on_terminate=True,
|
||||
info_to_inspect=f"""
|
||||
==File location==
|
||||
current-full.csv filepath: {file_path}
|
||||
|
||||
==Proposed Changes==
|
||||
Number of DomainInformation objects to change: {len(self.di_skipped)}
|
||||
The following DomainInformation objects will be modified if agency data exists in file: {self.di_skipped}
|
||||
""",
|
||||
prompt_title="Do you wish to patch skipped records?",
|
||||
)
|
||||
logger.info("Updating...")
|
||||
|
||||
file_data = self.read_current_full(file_path, separator)
|
||||
for di in self.di_skipped:
|
||||
domain_name = di.domain.name
|
||||
row = file_data.get(domain_name)
|
||||
fed_agency = None
|
||||
if row is not None and "agency" in row:
|
||||
fed_agency = row.get("agency")
|
||||
|
||||
# Determine if we should update this record or not.
|
||||
# If we don't get any data back, something went wrong.
|
||||
if fed_agency is not None:
|
||||
di.federal_agency = fed_agency
|
||||
self.di_to_update.append(di)
|
||||
if debug:
|
||||
logger.info(f"{TerminalColors.OKCYAN}" f"Updating {di}" f"{TerminalColors.ENDC}")
|
||||
else:
|
||||
self.di_failed_to_update.append(di)
|
||||
logger.error(
|
||||
f"{TerminalColors.FAIL}" f"Could not update {di}. No information found." f"{TerminalColors.ENDC}"
|
||||
)
|
||||
|
||||
# Bulk update the federal agency field in DomainInformation objects
|
||||
DomainInformation.objects.bulk_update(self.di_to_update, ["federal_agency"])
|
||||
|
||||
def read_current_full(self, file_path, separator):
|
||||
"""Reads the current-full.csv file and stores it in a dictionary"""
|
||||
with open(file_path, "r") as requested_file:
|
||||
old_reader = csv.DictReader(requested_file, delimiter=separator)
|
||||
# Some variants of current-full.csv have key casing differences for fields
|
||||
# such as "Domain name" or "Domain Name". This corrects that.
|
||||
reader = self.lowercase_fieldnames(old_reader)
|
||||
# Return a dictionary with the domain name as the key,
|
||||
# and the row information as the value
|
||||
dict_data = {}
|
||||
for row in reader:
|
||||
domain_name = row.get("domain name")
|
||||
if domain_name is not None:
|
||||
domain_name = domain_name.lower()
|
||||
dict_data[domain_name] = row
|
||||
|
||||
return dict_data
|
||||
|
||||
def lowercase_fieldnames(self, reader):
|
||||
"""Lowercases all field keys in a dictreader to account for potential casing differences"""
|
||||
for row in reader:
|
||||
yield {k.lower(): v for k, v in row.items()}
|
||||
|
||||
def log_script_run_summary(self, debug):
|
||||
"""Prints success, failed, and skipped counts, as well as
|
||||
all affected objects."""
|
||||
update_success_count = len(self.di_to_update)
|
||||
update_failed_count = len(self.di_failed_to_update)
|
||||
update_skipped_count = len(self.di_skipped)
|
||||
|
||||
# Prepare debug messages
|
||||
debug_messages = {
|
||||
"success": (f"{TerminalColors.OKCYAN}Updated: {self.di_to_update}{TerminalColors.ENDC}\n"),
|
||||
"skipped": (f"{TerminalColors.YELLOW}Skipped: {self.di_skipped}{TerminalColors.ENDC}\n"),
|
||||
"failed": (f"{TerminalColors.FAIL}Failed: {self.di_failed_to_update}{TerminalColors.ENDC}\n"),
|
||||
}
|
||||
|
||||
# Print out a list of everything that was changed, if we have any changes to log.
|
||||
# Otherwise, don't print anything.
|
||||
TerminalHelper.print_conditional(
|
||||
debug,
|
||||
f"{debug_messages.get('success') if update_success_count > 0 else ''}"
|
||||
f"{debug_messages.get('skipped') if update_skipped_count > 0 else ''}"
|
||||
f"{debug_messages.get('failed') if update_failed_count > 0 else ''}",
|
||||
)
|
||||
|
||||
if update_failed_count == 0 and update_skipped_count == 0:
|
||||
logger.info(
|
||||
f"""{TerminalColors.OKGREEN}
|
||||
============= FINISHED ===============
|
||||
Updated {update_success_count} DomainInformation entries
|
||||
{TerminalColors.ENDC}
|
||||
"""
|
||||
)
|
||||
elif update_failed_count == 0:
|
||||
logger.warning(
|
||||
f"""{TerminalColors.YELLOW}
|
||||
============= FINISHED ===============
|
||||
Updated {update_success_count} DomainInformation entries
|
||||
|
||||
----- SOME AGENCY DATA WAS NONE (WILL BE PATCHED AUTOMATICALLY) -----
|
||||
Skipped updating {update_skipped_count} DomainInformation entries
|
||||
{TerminalColors.ENDC}
|
||||
"""
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"""{TerminalColors.FAIL}
|
||||
============= FINISHED ===============
|
||||
Updated {update_success_count} DomainInformation entries
|
||||
|
||||
----- UPDATE FAILED -----
|
||||
Failed to update {update_failed_count} DomainInformation entries,
|
||||
Skipped updating {update_skipped_count} DomainInformation entries
|
||||
{TerminalColors.ENDC}
|
||||
"""
|
||||
)
|
68
src/registrar/management/commands/populate_first_ready.py
Normal file
68
src/registrar/management/commands/populate_first_ready.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import argparse
|
||||
import logging
|
||||
from typing import List
|
||||
from django.core.management import BaseCommand
|
||||
from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper, ScriptDataHelper
|
||||
from registrar.models import Domain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Loops through each valid Domain object and updates its first_created value"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.to_update: List[Domain] = []
|
||||
self.failed_to_update: List[Domain] = []
|
||||
self.skipped: List[Domain] = []
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Adds command line arguments"""
|
||||
parser.add_argument("--debug", action=argparse.BooleanOptionalAction)
|
||||
|
||||
def handle(self, **kwargs):
|
||||
"""Loops through each valid Domain object and updates its first_created value"""
|
||||
debug = kwargs.get("debug")
|
||||
# Get all valid domains
|
||||
valid_states = [Domain.State.READY, Domain.State.ON_HOLD, Domain.State.DELETED]
|
||||
domains = Domain.objects.filter(first_ready=None, state__in=valid_states)
|
||||
|
||||
# Code execution will stop here if the user prompts "N"
|
||||
TerminalHelper.prompt_for_execution(
|
||||
system_exit_on_terminate=True,
|
||||
info_to_inspect=f"""
|
||||
==Proposed Changes==
|
||||
Number of Domain objects to change: {len(domains)}
|
||||
""",
|
||||
prompt_title="Do you wish to patch first_ready data?",
|
||||
)
|
||||
logger.info("Updating...")
|
||||
|
||||
for domain in domains:
|
||||
try:
|
||||
self.update_first_ready_for_domain(domain, debug)
|
||||
except Exception as err:
|
||||
self.failed_to_update.append(domain)
|
||||
logger.error(err)
|
||||
logger.error(f"{TerminalColors.FAIL}" f"Failed to update {domain}" f"{TerminalColors.ENDC}")
|
||||
|
||||
# Do a bulk update on the first_ready field
|
||||
ScriptDataHelper.bulk_update_fields(Domain, self.to_update, ["first_ready"])
|
||||
|
||||
# Log what happened
|
||||
TerminalHelper.log_script_run_summary(self.to_update, self.failed_to_update, self.skipped, debug)
|
||||
|
||||
def update_first_ready_for_domain(self, domain: Domain, debug: bool):
|
||||
"""Grabs the created_at field and associates it with the first_ready column.
|
||||
Appends the result to the to_update list."""
|
||||
created_at = domain.created_at
|
||||
if created_at is not None:
|
||||
domain.first_ready = domain.created_at
|
||||
self.to_update.append(domain)
|
||||
if debug:
|
||||
logger.info(f"Updating {domain}")
|
||||
else:
|
||||
self.skipped.append(domain)
|
||||
if debug:
|
||||
logger.warning(f"Skipped updating {domain}")
|
|
@ -1,6 +1,7 @@
|
|||
from enum import Enum
|
||||
import logging
|
||||
import sys
|
||||
from django.core.paginator import Paginator
|
||||
from typing import List
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -41,7 +42,94 @@ class TerminalColors:
|
|||
BackgroundLightYellow = "\033[103m"
|
||||
|
||||
|
||||
class ScriptDataHelper:
|
||||
"""Helper method with utilities to speed up development of scripts that do DB operations"""
|
||||
|
||||
@staticmethod
|
||||
def bulk_update_fields(model_class, update_list, fields_to_update, batch_size=1000):
|
||||
"""
|
||||
This function performs a bulk update operation on a specified Django model class in batches.
|
||||
It uses Django's Paginator to handle large datasets in a memory-efficient manner.
|
||||
|
||||
Parameters:
|
||||
model_class: The Django model class that you want to perform the bulk update on.
|
||||
This should be the actual class, not a string of the class name.
|
||||
|
||||
update_list: A list of model instances that you want to update. Each instance in the list
|
||||
should already have the updated values set on the instance.
|
||||
|
||||
batch_size: The maximum number of model instances to update in a single database query.
|
||||
Defaults to 1000. If you're dealing with models that have a large number of fields,
|
||||
or large field values, you may need to decrease this value to prevent out-of-memory errors.
|
||||
|
||||
fields_to_update: Specifies which fields to update.
|
||||
|
||||
Usage:
|
||||
bulk_update_fields(Domain, page.object_list, ["first_ready"])
|
||||
"""
|
||||
# Create a Paginator object. Bulk_update on the full dataset
|
||||
# is too memory intensive for our current app config, so we can chunk this data instead.
|
||||
paginator = Paginator(update_list, batch_size)
|
||||
for page_num in paginator.page_range:
|
||||
page = paginator.page(page_num)
|
||||
model_class.objects.bulk_update(page.object_list, fields_to_update)
|
||||
|
||||
|
||||
class TerminalHelper:
|
||||
@staticmethod
|
||||
def log_script_run_summary(to_update, failed_to_update, skipped, debug: bool):
|
||||
"""Prints success, failed, and skipped counts, as well as
|
||||
all affected objects."""
|
||||
update_success_count = len(to_update)
|
||||
update_failed_count = len(failed_to_update)
|
||||
update_skipped_count = len(skipped)
|
||||
|
||||
# Prepare debug messages
|
||||
debug_messages = {
|
||||
"success": (f"{TerminalColors.OKCYAN}Updated: {to_update}{TerminalColors.ENDC}\n"),
|
||||
"skipped": (f"{TerminalColors.YELLOW}Skipped: {skipped}{TerminalColors.ENDC}\n"),
|
||||
"failed": (f"{TerminalColors.FAIL}Failed: {failed_to_update}{TerminalColors.ENDC}\n"),
|
||||
}
|
||||
|
||||
# Print out a list of everything that was changed, if we have any changes to log.
|
||||
# Otherwise, don't print anything.
|
||||
TerminalHelper.print_conditional(
|
||||
debug,
|
||||
f"{debug_messages.get('success') if update_success_count > 0 else ''}"
|
||||
f"{debug_messages.get('skipped') if update_skipped_count > 0 else ''}"
|
||||
f"{debug_messages.get('failed') if update_failed_count > 0 else ''}",
|
||||
)
|
||||
|
||||
if update_failed_count == 0 and update_skipped_count == 0:
|
||||
logger.info(
|
||||
f"""{TerminalColors.OKGREEN}
|
||||
============= FINISHED ===============
|
||||
Updated {update_success_count} entries
|
||||
{TerminalColors.ENDC}
|
||||
"""
|
||||
)
|
||||
elif update_failed_count == 0:
|
||||
logger.warning(
|
||||
f"""{TerminalColors.YELLOW}
|
||||
============= FINISHED ===============
|
||||
Updated {update_success_count} entries
|
||||
----- SOME DATA WAS INVALID (NEEDS MANUAL PATCHING) -----
|
||||
Skipped updating {update_skipped_count} entries
|
||||
{TerminalColors.ENDC}
|
||||
"""
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"""{TerminalColors.FAIL}
|
||||
============= FINISHED ===============
|
||||
Updated {update_success_count} entries
|
||||
----- UPDATE FAILED -----
|
||||
Failed to update {update_failed_count} entries,
|
||||
Skipped updating {update_skipped_count} entries
|
||||
{TerminalColors.ENDC}
|
||||
"""
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def query_yes_no(question: str, default="yes"):
|
||||
"""Ask a yes/no question via raw_input() and return their answer.
|
||||
|
|
|
@ -653,13 +653,11 @@ class DomainApplication(TimeStampedModel):
|
|||
def in_review(self):
|
||||
"""Investigate an application that has been submitted.
|
||||
|
||||
As a side effect, an email notification is sent."""
|
||||
|
||||
self._send_status_update_email(
|
||||
"application in review",
|
||||
"emails/status_change_in_review.txt",
|
||||
"emails/status_change_in_review_subject.txt",
|
||||
)
|
||||
This action is logged."""
|
||||
literal = DomainApplication.ApplicationStatus.IN_REVIEW
|
||||
# Check if the tuple exists, then grab its value
|
||||
in_review = literal if literal is not None else "In Review"
|
||||
logger.info(f"A status change occurred. {self} was changed to '{in_review}'")
|
||||
|
||||
@transition(
|
||||
field="status",
|
||||
|
@ -674,13 +672,11 @@ class DomainApplication(TimeStampedModel):
|
|||
def action_needed(self):
|
||||
"""Send back an application that is under investigation or rejected.
|
||||
|
||||
As a side effect, an email notification is sent."""
|
||||
|
||||
self._send_status_update_email(
|
||||
"action needed",
|
||||
"emails/status_change_action_needed.txt",
|
||||
"emails/status_change_action_needed_subject.txt",
|
||||
)
|
||||
This action is logged."""
|
||||
literal = DomainApplication.ApplicationStatus.ACTION_NEEDED
|
||||
# Check if the tuple is setup correctly, then grab its value
|
||||
action_needed = literal if literal is not None else "Action Needed"
|
||||
logger.info(f"A status change occurred. {self} was changed to '{action_needed}'")
|
||||
|
||||
@transition(
|
||||
field="status",
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
{% load field_helpers %}
|
||||
|
||||
{% block form_instructions %}
|
||||
<p>We’d like to know more about your organization. Include the following in your response: </p>
|
||||
<p>To help us determine your eligibility for a .gov domain, we need to know more about your organization. For example:</p>
|
||||
|
||||
<ul class="usa-list">
|
||||
<li>The type of work your organization does </li>
|
||||
<li>How your organization is a government organization that is independent of a state government </li>
|
||||
<li>Include links to authorizing legislation, applicable bylaws or charter, or other documentation to support your claims.</li>
|
||||
<li>How your organization operates independently from a state government</li>
|
||||
<li>A description of the specialized, essential services you offer (if applicable)</li>
|
||||
<li>Links to authorizing legislation, applicable bylaws or charter, or other documentation to support your claims</li>
|
||||
</ul>
|
||||
</p>
|
||||
<h2>What can you tell us about your organization?</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block form_required_fields_help_text %}
|
||||
|
@ -20,4 +22,4 @@
|
|||
{% with attr_maxlength=1000 add_label_class="usa-sr-only" %}
|
||||
{% input_with_errors forms.0.about_your_organization %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{% include "includes/ao_example.html" %}
|
||||
</div>
|
||||
|
||||
<p>We typically don’t reach out to the authorizing official, but if contact is necessary, our practice is to coordinate first with you, the requestor. Read more about <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
|
||||
<p>We typically don’t reach out to the authorizing official, but if contact is necessary, our practice is to coordinate with you, the requestor, first.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
{% load static field_helpers %}
|
||||
|
||||
{% block form_instructions %}
|
||||
<p>Enter your organization’s current public website, if you have one. For example,
|
||||
www.city.com. We can better evaluate your domain request if we know about domains
|
||||
you’re already using. If you already have any .gov domains please include them. This question is optional.</p>
|
||||
<p>We can better evaluate your request if we know about domains you’re already using.</p>
|
||||
<h2>What are the current websites for your organization?</h2>
|
||||
<p>Enter your organization’s current public websites. If you already have a .gov domain, include that in your list. This question is optional.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block form_required_fields_help_text %}
|
||||
|
|
|
@ -2,15 +2,12 @@
|
|||
{% load field_helpers %}
|
||||
|
||||
{% block form_instructions %}
|
||||
<h2 class="margin-bottom-05">
|
||||
What is the name and mailing address of your organization?
|
||||
</h2>
|
||||
<p>If your domain request is approved, the name of your organization and your city/state will be listed in <a href="https://beta.get.gov/about/data/" target="_blank">.gov’s public data.</a></p>
|
||||
|
||||
<p>Enter the name of the organization you represent. Your organization might be part
|
||||
of a larger entity. If so, enter information about your part of the larger entity.</p>
|
||||
<h2>What is the name and mailing address of the organization you represent?</h2>
|
||||
|
||||
<p>Your organization might be part of a larger entity. If so, enter the name of your part of the larger entity. </p>
|
||||
|
||||
<p>If your domain request is approved, the name of your organization will be publicly
|
||||
listed as the domain registrant.</p>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
@ -43,4 +40,4 @@
|
|||
{% input_with_errors forms.0.urbanization %}
|
||||
|
||||
</fieldset>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
{% load field_helpers %}
|
||||
|
||||
{% block form_instructions %}
|
||||
<h2 class="margin-bottom-05">Is your organization an election office?</h2>
|
||||
|
||||
|
||||
<p>An election office is a government entity whose <em>primary</em> responsibility is overseeing elections and/or conducting voter registration.</p>
|
||||
<p>An election office is a government entity whose primary responsibility is overseeing elections and/or conducting voter registration. If your organization is an election office, we'll prioritize your request.</p>
|
||||
|
||||
<h2>Is your organization an election office?</h2>
|
||||
|
||||
<p>Answer “yes” only if the <em>main purpose</em> of your organization is to serve as an election office.</p>
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
</fieldset>
|
||||
|
||||
<div id="other-employees">
|
||||
<div id="other-employees" class="other-contacts-form">
|
||||
{% include "includes/required_fields.html" %}
|
||||
{{ forms.1.management_form }}
|
||||
{# forms.1 is a formset and this iterates over its forms #}
|
||||
|
@ -51,7 +51,7 @@
|
|||
{{ form.DELETE }}
|
||||
{% endif %}
|
||||
|
||||
<div class="padding-top-1 clear-both">
|
||||
<div class="clear-both">
|
||||
{% input_with_errors form.first_name %}
|
||||
</div>
|
||||
|
||||
|
@ -84,9 +84,9 @@
|
|||
</div>
|
||||
|
||||
<div id="no-other-employees">
|
||||
<fieldset class="usa-fieldset margin-top-2">
|
||||
<fieldset class="usa-fieldset margin-top-4">
|
||||
<legend>
|
||||
<h2>No other employees from your organization?</h2>
|
||||
<h2 class="margin-bottom-0">No other employees from your organization?</h2>
|
||||
</legend>
|
||||
<p>You don't need to provide names of other employees now, but it may
|
||||
slow down our assessment of your eligibility. Describe why there are
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
{% extends 'application_form.html' %}
|
||||
{% load field_helpers %}
|
||||
|
||||
{% block form_instructions %}
|
||||
<p>To help us determine your eligibility for a .gov domain, we need to know more about your tribal government.</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block form_fields %}
|
||||
|
||||
{% with sublabel_text="Please include the entire name of your tribe as recognized by the Bureau of Indian Affairs." %}
|
||||
{% with link_text="Bureau of Indian Affairs" %}
|
||||
{% with link_href="https://www.federalregister.gov/documents/2023/01/12/2023-00504/indian-entities-recognized-by-and-eligible-to-receive-services-from-the-united-states-bureau-of" %}
|
||||
{% with external_link="true" target_blank="true" %}
|
||||
{% input_with_errors forms.0.tribe_name %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
<h2>What is the name of the tribe you represent?</h2>
|
||||
<p>Please include the full name of your tribe as recognized by the <a rel="noopener noreferrer" class="usa-link usa-link--external" href="https://www.federalregister.gov/documents/2024/01/08/2024-00109/indian-entities-recognized-by-and-eligible-to-receive-services-from-the-united-states-bureau-of" target="_blank">Bureau of Indian Affairs</a>.</p>
|
||||
|
||||
{% with external_link="true" target_blank="true" %}
|
||||
{% input_with_errors forms.0.tribe_name %}
|
||||
{% endwith %}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend class="usa-legend">
|
||||
<p>Is your organization a federally-recognized tribe or a state-recognized tribe? Check all that apply.
|
||||
<abbr class="usa-hint usa-hint--required" title="required">*</abbr></p>
|
||||
<h2>Is your organization a federally-recognized tribe or a state-recognized tribe?</h2>
|
||||
</legend>
|
||||
<p>Check all that apply. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></p>
|
||||
{% input_with_errors forms.0.federally_recognized_tribe %}
|
||||
{% input_with_errors forms.0.state_recognized_tribe %}
|
||||
</fieldset>
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
<span class="text-bold text-primary-darker">
|
||||
Status:
|
||||
</span>
|
||||
{% if domain.is_expired %}
|
||||
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
||||
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
||||
Expired
|
||||
{% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%}
|
||||
{% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %}
|
||||
DNS needed
|
||||
{% else %}
|
||||
{{ domain.state|title }}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
{% include "includes/required_fields.html" %}
|
||||
|
||||
<form class="usa-form usa-form--extra-large" method="post" novalidate id="form-container">
|
||||
<form class="usa-form usa-form--extra-large ds-data-form" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
{{ formset.management_form }}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
{% include "includes/required_fields.html" %}
|
||||
|
||||
<form class="usa-form usa-form--extra-large" method="post" novalidate id="form-container">
|
||||
<form class="usa-form usa-form--extra-large nameservers-form" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
{{ formset.management_form }}
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||
Hi {{ application.submitter.first_name }}.
|
||||
|
||||
We've identified an action needed to complete the review of your .gov domain request.
|
||||
|
||||
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
||||
REQUEST RECEIVED ON: {{ application.submission_date|date }}
|
||||
REQUEST #: {{ application.id }}
|
||||
STATUS: Action needed
|
||||
|
||||
|
||||
NEED TO MAKE CHANGES?
|
||||
|
||||
If you need to change your request you have to first withdraw it. Once you
|
||||
withdraw the request you can edit it and submit it again. Changing your request
|
||||
might add to the wait time. Learn more about withdrawing your request.
|
||||
<https://get.gov/help/domain-requests/#withdraw-your-domain-request>.
|
||||
|
||||
|
||||
NEXT STEPS
|
||||
|
||||
- You will receive a separate email from our team that provides details about the action needed.
|
||||
You may need to update your application or provide additional information.
|
||||
|
||||
- If you do not receive a separate email with these details within one business day, please contact us:
|
||||
<https://forms.office.com/pages/responsepage.aspx?id=bOfNPG2UEkq7evydCEI1SqHke9Gh6wJEl3kQ5EjWUKlUQzRJWDlBNTBCQUxTTzBaNlhTWURSSTBLTC4u>
|
||||
|
||||
|
||||
THANK YOU
|
||||
|
||||
.Gov helps the public identify official, trusted information. Thank you for
|
||||
requesting a .gov domain.
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
{% include 'emails/includes/application_summary.txt' %}
|
||||
----------------------------------------------------------------
|
||||
|
||||
The .gov team
|
||||
Contact us: <https://get.gov/contact/>
|
||||
Visit <https://get.gov>
|
||||
{% endautoescape %}
|
|
@ -1 +0,0 @@
|
|||
Action needed for your .gov domain request
|
|
@ -1,43 +0,0 @@
|
|||
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||
Hi {{ application.submitter.first_name }}.
|
||||
|
||||
Your .gov domain request is being reviewed.
|
||||
|
||||
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
||||
REQUEST RECEIVED ON: {{ application.submission_date|date }}
|
||||
REQUEST #: {{ application.id }}
|
||||
STATUS: In review
|
||||
|
||||
|
||||
NEED TO MAKE CHANGES?
|
||||
|
||||
If you need to change your request you have to first withdraw it. Once you
|
||||
withdraw the request you can edit it and submit it again. Changing your request
|
||||
might add to the wait time. Learn more about withdrawing your request.
|
||||
<https://get.gov/help/domain-requests/#withdraw-your-domain-request>.
|
||||
|
||||
|
||||
NEXT STEPS
|
||||
|
||||
- We’re reviewing your request. This usually takes 20 business days.
|
||||
|
||||
- You can check the status of your request at any time.
|
||||
<https://manage.get.gov/application/{{ application.id }}>
|
||||
|
||||
- We’ll email you with questions or when we complete our review.
|
||||
|
||||
|
||||
THANK YOU
|
||||
|
||||
.Gov helps the public identify official, trusted information. Thank you for
|
||||
requesting a .gov domain.
|
||||
|
||||
----------------------------------------------------------------
|
||||
|
||||
{% include 'emails/includes/application_summary.txt' %}
|
||||
----------------------------------------------------------------
|
||||
|
||||
The .gov team
|
||||
Contact us: <https://get.gov/contact/>
|
||||
Visit <https://get.gov>
|
||||
{% endautoescape %}
|
|
@ -1 +0,0 @@
|
|||
Your .gov domain request is being reviewed
|
|
@ -52,9 +52,10 @@
|
|||
</th>
|
||||
<td data-sort-value="{{ domain.expiration_date|date:"U" }}" data-label="Expires">{{ domain.expiration_date|date }}</td>
|
||||
<td data-label="Status">
|
||||
{% if domain.is_expired %}
|
||||
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
||||
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
||||
Expired
|
||||
{% elif domain.state == "unknown" or domain.state == "dns needed"%}
|
||||
{% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %}
|
||||
DNS needed
|
||||
{% else %}
|
||||
{{ domain.state|title }}
|
||||
|
|
|
@ -743,6 +743,25 @@ class MockEppLib(TestCase):
|
|||
],
|
||||
)
|
||||
|
||||
mockVerisignDataInfoContact = mockDataInfoDomain.dummyInfoContactResultData(
|
||||
"defaultVeri", "registrar@dotgov.gov", datetime.datetime(2023, 5, 25, 19, 45, 35), "lastPw"
|
||||
)
|
||||
InfoDomainWithVerisignSecurityContact = fakedEppObject(
|
||||
"fakepw",
|
||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
||||
contacts=[
|
||||
common.DomainContact(
|
||||
contact="defaultVeri",
|
||||
type=PublicContact.ContactTypeChoices.SECURITY,
|
||||
)
|
||||
],
|
||||
hosts=["fake.host.com"],
|
||||
statuses=[
|
||||
common.Status(state="serverTransferProhibited", description="", lang="en"),
|
||||
common.Status(state="inactive", description="", lang="en"),
|
||||
],
|
||||
)
|
||||
|
||||
InfoDomainWithDefaultTechnicalContact = fakedEppObject(
|
||||
"fakepw",
|
||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
||||
|
@ -1058,6 +1077,7 @@ class MockEppLib(TestCase):
|
|||
"freeman.gov": (self.InfoDomainWithContacts, None),
|
||||
"threenameserversDomain.gov": (self.infoDomainThreeHosts, None),
|
||||
"defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None),
|
||||
"adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None),
|
||||
"defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None),
|
||||
"justnameserver.com": (self.justNameserver, None),
|
||||
}
|
||||
|
@ -1087,6 +1107,8 @@ class MockEppLib(TestCase):
|
|||
mocked_result = self.mockDefaultSecurityContact
|
||||
case "defaultTech":
|
||||
mocked_result = self.mockDefaultTechnicalContact
|
||||
case "defaultVeri":
|
||||
mocked_result = self.mockVerisignDataInfoContact
|
||||
case _:
|
||||
# Default contact return
|
||||
mocked_result = self.mockDataInfoContact
|
||||
|
|
|
@ -457,44 +457,6 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
|
||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_save_model_sends_in_review_email(self):
|
||||
# make sure there is no user with this email
|
||||
EMAIL = "mayor@igorville.gov"
|
||||
User.objects.filter(email=EMAIL).delete()
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||
with less_console_noise():
|
||||
# Create a sample application
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED)
|
||||
|
||||
# Create a mock request
|
||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||
|
||||
# Modify the application's property
|
||||
application.status = DomainApplication.ApplicationStatus.IN_REVIEW
|
||||
|
||||
# Use the model admin's save_model method
|
||||
self.admin.save_model(request, application, form=None, change=True)
|
||||
|
||||
# Access the arguments passed to send_email
|
||||
call_args = self.mock_client.EMAILS_SENT
|
||||
kwargs = call_args[0]["kwargs"]
|
||||
|
||||
# Retrieve the email details from the arguments
|
||||
from_email = kwargs.get("FromEmailAddress")
|
||||
to_email = kwargs["Destination"]["ToAddresses"][0]
|
||||
email_content = kwargs["Content"]
|
||||
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
||||
|
||||
# Assert or perform other checks on the email details
|
||||
expected_string = "Your .gov domain request is being reviewed."
|
||||
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
||||
self.assertEqual(to_email, EMAIL)
|
||||
self.assertIn(expected_string, email_body)
|
||||
|
||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_save_model_sends_approved_email(self):
|
||||
# make sure there is no user with this email
|
||||
|
@ -556,44 +518,6 @@ class TestDomainApplicationAdmin(MockEppLib):
|
|||
# Test that approved domain exists and equals requested domain
|
||||
self.assertEqual(application.requested_domain.name, application.approved_domain.name)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_save_model_sends_action_needed_email(self):
|
||||
# make sure there is no user with this email
|
||||
EMAIL = "mayor@igorville.gov"
|
||||
User.objects.filter(email=EMAIL).delete()
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||
with less_console_noise():
|
||||
# Create a sample application
|
||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||
|
||||
# Create a mock request
|
||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||
|
||||
# Modify the application's property
|
||||
application.status = DomainApplication.ApplicationStatus.ACTION_NEEDED
|
||||
|
||||
# Use the model admin's save_model method
|
||||
self.admin.save_model(request, application, form=None, change=True)
|
||||
|
||||
# Access the arguments passed to send_email
|
||||
call_args = self.mock_client.EMAILS_SENT
|
||||
kwargs = call_args[0]["kwargs"]
|
||||
|
||||
# Retrieve the email details from the arguments
|
||||
from_email = kwargs.get("FromEmailAddress")
|
||||
to_email = kwargs["Destination"]["ToAddresses"][0]
|
||||
email_content = kwargs["Content"]
|
||||
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
||||
|
||||
# Assert or perform other checks on the email details
|
||||
expected_string = "We've identified an action needed to complete the review of your .gov domain request."
|
||||
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
||||
self.assertEqual(to_email, EMAIL)
|
||||
self.assertIn(expected_string, email_body)
|
||||
|
||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||
|
||||
@boto3_mocking.patching
|
||||
def test_save_model_sends_rejected_email(self):
|
||||
# make sure there is no user with this email
|
||||
|
|
443
src/registrar/tests/test_management_scripts.py
Normal file
443
src/registrar/tests/test_management_scripts.py
Normal file
|
@ -0,0 +1,443 @@
|
|||
import copy
|
||||
import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from registrar.models import (
|
||||
User,
|
||||
Domain,
|
||||
DomainInvitation,
|
||||
TransitionDomain,
|
||||
DomainInformation,
|
||||
UserDomainRole,
|
||||
)
|
||||
|
||||
from django.core.management import call_command
|
||||
from unittest.mock import patch
|
||||
|
||||
from .common import MockEppLib
|
||||
|
||||
|
||||
class TestPopulateFirstReady(TestCase):
|
||||
"""Tests for the populate_first_ready script"""
|
||||
|
||||
def setUp(self):
|
||||
"""Creates a fake domain object"""
|
||||
super().setUp()
|
||||
self.ready_domain, _ = Domain.objects.get_or_create(name="fakeready.gov", state=Domain.State.READY)
|
||||
self.dns_needed_domain, _ = Domain.objects.get_or_create(name="fakedns.gov", state=Domain.State.DNS_NEEDED)
|
||||
self.deleted_domain, _ = Domain.objects.get_or_create(name="fakedeleted.gov", state=Domain.State.DELETED)
|
||||
self.hold_domain, _ = Domain.objects.get_or_create(name="fakehold.gov", state=Domain.State.ON_HOLD)
|
||||
self.unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN)
|
||||
|
||||
# Set a ready_at date for testing purposes
|
||||
self.ready_at_date = datetime.date(2022, 12, 31)
|
||||
|
||||
def tearDown(self):
|
||||
"""Deletes all DB objects related to migrations"""
|
||||
super().tearDown()
|
||||
|
||||
# Delete domains
|
||||
Domain.objects.all().delete()
|
||||
|
||||
def run_populate_first_ready(self):
|
||||
"""
|
||||
This method executes the populate_first_ready command.
|
||||
|
||||
The 'call_command' function from Django's management framework is then used to
|
||||
execute the populate_first_ready command with the specified arguments.
|
||||
"""
|
||||
with patch(
|
||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||
return_value=True,
|
||||
):
|
||||
call_command("populate_first_ready")
|
||||
|
||||
def test_populate_first_ready_state_ready(self):
|
||||
"""
|
||||
Tests that the populate_first_ready works as expected for the state 'ready'
|
||||
"""
|
||||
# Set the created at date
|
||||
self.ready_domain.created_at = self.ready_at_date
|
||||
self.ready_domain.save()
|
||||
|
||||
desired_domain = copy.deepcopy(self.ready_domain)
|
||||
|
||||
desired_domain.first_ready = self.ready_at_date
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_populate_first_ready()
|
||||
|
||||
self.assertEqual(desired_domain, self.ready_domain)
|
||||
|
||||
# Explicitly test the first_ready date
|
||||
first_ready = Domain.objects.filter(name="fakeready.gov").get().first_ready
|
||||
self.assertEqual(first_ready, self.ready_at_date)
|
||||
|
||||
def test_populate_first_ready_state_deleted(self):
|
||||
"""
|
||||
Tests that the populate_first_ready works as expected for the state 'deleted'
|
||||
"""
|
||||
# Set the created at date
|
||||
self.deleted_domain.created_at = self.ready_at_date
|
||||
self.deleted_domain.save()
|
||||
|
||||
desired_domain = copy.deepcopy(self.deleted_domain)
|
||||
|
||||
desired_domain.first_ready = self.ready_at_date
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_populate_first_ready()
|
||||
|
||||
self.assertEqual(desired_domain, self.deleted_domain)
|
||||
|
||||
# Explicitly test the first_ready date
|
||||
first_ready = Domain.objects.filter(name="fakedeleted.gov").get().first_ready
|
||||
self.assertEqual(first_ready, self.ready_at_date)
|
||||
|
||||
def test_populate_first_ready_state_dns_needed(self):
|
||||
"""
|
||||
Tests that the populate_first_ready doesn't make changes when a domain's state is 'dns_needed'
|
||||
"""
|
||||
# Set the created at date
|
||||
self.dns_needed_domain.created_at = self.ready_at_date
|
||||
self.dns_needed_domain.save()
|
||||
|
||||
desired_domain = copy.deepcopy(self.dns_needed_domain)
|
||||
|
||||
desired_domain.first_ready = None
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_populate_first_ready()
|
||||
|
||||
current_domain = self.dns_needed_domain
|
||||
# The object should largely be unaltered (does not test first_ready)
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
first_ready = Domain.objects.filter(name="fakedns.gov").get().first_ready
|
||||
|
||||
# Explicitly test the first_ready date
|
||||
self.assertNotEqual(first_ready, self.ready_at_date)
|
||||
self.assertEqual(first_ready, None)
|
||||
|
||||
def test_populate_first_ready_state_on_hold(self):
|
||||
"""
|
||||
Tests that the populate_first_ready works as expected for the state 'on_hold'
|
||||
"""
|
||||
self.hold_domain.created_at = self.ready_at_date
|
||||
self.hold_domain.save()
|
||||
|
||||
desired_domain = copy.deepcopy(self.hold_domain)
|
||||
desired_domain.first_ready = self.ready_at_date
|
||||
|
||||
# Run the update first ready_at script
|
||||
self.run_populate_first_ready()
|
||||
|
||||
current_domain = self.hold_domain
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the first_ready date
|
||||
first_ready = Domain.objects.filter(name="fakehold.gov").get().first_ready
|
||||
self.assertEqual(first_ready, self.ready_at_date)
|
||||
|
||||
def test_populate_first_ready_state_unknown(self):
|
||||
"""
|
||||
Tests that the populate_first_ready works as expected for the state 'unknown'
|
||||
"""
|
||||
# Set the created at date
|
||||
self.unknown_domain.created_at = self.ready_at_date
|
||||
self.unknown_domain.save()
|
||||
|
||||
desired_domain = copy.deepcopy(self.unknown_domain)
|
||||
desired_domain.first_ready = None
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_populate_first_ready()
|
||||
|
||||
current_domain = self.unknown_domain
|
||||
|
||||
# The object should largely be unaltered (does not test first_ready)
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the first_ready date
|
||||
first_ready = Domain.objects.filter(name="fakeunknown.gov").get().first_ready
|
||||
self.assertNotEqual(first_ready, self.ready_at_date)
|
||||
self.assertEqual(first_ready, None)
|
||||
|
||||
|
||||
class TestPatchAgencyInfo(TestCase):
|
||||
def setUp(self):
|
||||
self.user, _ = User.objects.get_or_create(username="testuser")
|
||||
self.domain, _ = Domain.objects.get_or_create(name="testdomain.gov")
|
||||
self.domain_info, _ = DomainInformation.objects.get_or_create(domain=self.domain, creator=self.user)
|
||||
self.transition_domain, _ = TransitionDomain.objects.get_or_create(
|
||||
domain_name="testdomain.gov", federal_agency="test agency"
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
Domain.objects.all().delete()
|
||||
DomainInformation.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
TransitionDomain.objects.all().delete()
|
||||
|
||||
@patch("registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", return_value=True)
|
||||
def call_patch_federal_agency_info(self, mock_prompt):
|
||||
"""Calls the patch_federal_agency_info command and mimics a keypress"""
|
||||
call_command("patch_federal_agency_info", "registrar/tests/data/fake_current_full.csv", debug=True)
|
||||
|
||||
def test_patch_agency_info(self):
|
||||
"""
|
||||
Tests that the `patch_federal_agency_info` command successfully
|
||||
updates the `federal_agency` field
|
||||
of a `DomainInformation` object when the corresponding
|
||||
`TransitionDomain` object has a valid `federal_agency`.
|
||||
"""
|
||||
|
||||
# Ensure that the federal_agency is None
|
||||
self.assertEqual(self.domain_info.federal_agency, None)
|
||||
|
||||
self.call_patch_federal_agency_info()
|
||||
|
||||
# Reload the domain_info object from the database
|
||||
self.domain_info.refresh_from_db()
|
||||
|
||||
# Check that the federal_agency field was updated
|
||||
self.assertEqual(self.domain_info.federal_agency, "test agency")
|
||||
|
||||
def test_patch_agency_info_skip(self):
|
||||
"""
|
||||
Tests that the `patch_federal_agency_info` command logs a warning and
|
||||
does not update the `federal_agency` field
|
||||
of a `DomainInformation` object when the corresponding
|
||||
`TransitionDomain` object does not exist.
|
||||
"""
|
||||
# Set federal_agency to None to simulate a skip
|
||||
self.transition_domain.federal_agency = None
|
||||
self.transition_domain.save()
|
||||
|
||||
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context:
|
||||
self.call_patch_federal_agency_info()
|
||||
|
||||
# Check that the correct log message was output
|
||||
self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0])
|
||||
|
||||
# Reload the domain_info object from the database
|
||||
self.domain_info.refresh_from_db()
|
||||
|
||||
# Check that the federal_agency field was not updated
|
||||
self.assertIsNone(self.domain_info.federal_agency)
|
||||
|
||||
def test_patch_agency_info_skip_updates_data(self):
|
||||
"""
|
||||
Tests that the `patch_federal_agency_info` command logs a warning but
|
||||
updates the DomainInformation object, because a record exists in the
|
||||
provided current-full.csv file.
|
||||
"""
|
||||
# Set federal_agency to None to simulate a skip
|
||||
self.transition_domain.federal_agency = None
|
||||
self.transition_domain.save()
|
||||
|
||||
# Change the domain name to something parsable in the .csv
|
||||
self.domain.name = "cdomain1.gov"
|
||||
self.domain.save()
|
||||
|
||||
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context:
|
||||
self.call_patch_federal_agency_info()
|
||||
|
||||
# Check that the correct log message was output
|
||||
self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0])
|
||||
|
||||
# Reload the domain_info object from the database
|
||||
self.domain_info.refresh_from_db()
|
||||
|
||||
# Check that the federal_agency field was not updated
|
||||
self.assertEqual(self.domain_info.federal_agency, "World War I Centennial Commission")
|
||||
|
||||
def test_patch_agency_info_skips_valid_domains(self):
|
||||
"""
|
||||
Tests that the `patch_federal_agency_info` command logs INFO and
|
||||
does not update the `federal_agency` field
|
||||
of a `DomainInformation` object
|
||||
"""
|
||||
self.domain_info.federal_agency = "unchanged"
|
||||
self.domain_info.save()
|
||||
|
||||
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="INFO") as context:
|
||||
self.call_patch_federal_agency_info()
|
||||
|
||||
# Check that the correct log message was output
|
||||
self.assertIn("FINISHED", context.output[1])
|
||||
|
||||
# Reload the domain_info object from the database
|
||||
self.domain_info.refresh_from_db()
|
||||
|
||||
# Check that the federal_agency field was not updated
|
||||
self.assertEqual(self.domain_info.federal_agency, "unchanged")
|
||||
|
||||
|
||||
class TestExtendExpirationDates(MockEppLib):
|
||||
def setUp(self):
|
||||
"""Defines the file name of migration_json and the folder its contained in"""
|
||||
super().setUp()
|
||||
# Create a valid domain that is updatable
|
||||
Domain.objects.get_or_create(
|
||||
name="waterbutpurple.gov", state=Domain.State.READY, expiration_date=datetime.date(2023, 11, 15)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="testytester@mail.com",
|
||||
domain_name="waterbutpurple.gov",
|
||||
epp_expiration_date=datetime.date(2023, 11, 15),
|
||||
)
|
||||
# Create a domain with an invalid expiration date
|
||||
Domain.objects.get_or_create(
|
||||
name="fake.gov", state=Domain.State.READY, expiration_date=datetime.date(2022, 5, 25)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="themoonisactuallycheese@mail.com",
|
||||
domain_name="fake.gov",
|
||||
epp_expiration_date=datetime.date(2022, 5, 25),
|
||||
)
|
||||
# Create a domain with an invalid state
|
||||
Domain.objects.get_or_create(
|
||||
name="fakeneeded.gov", state=Domain.State.DNS_NEEDED, expiration_date=datetime.date(2023, 11, 15)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="fakeneeded@mail.com",
|
||||
domain_name="fakeneeded.gov",
|
||||
epp_expiration_date=datetime.date(2023, 11, 15),
|
||||
)
|
||||
# Create a domain with a date greater than the maximum
|
||||
Domain.objects.get_or_create(
|
||||
name="fakemaximum.gov", state=Domain.State.READY, expiration_date=datetime.date(2024, 12, 31)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="fakemaximum@mail.com",
|
||||
domain_name="fakemaximum.gov",
|
||||
epp_expiration_date=datetime.date(2024, 12, 31),
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
"""Deletes all DB objects related to migrations"""
|
||||
super().tearDown()
|
||||
# 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_extend_expiration_dates(self):
|
||||
"""
|
||||
This method executes the extend_expiration_dates command.
|
||||
|
||||
The 'call_command' function from Django's management framework is then used to
|
||||
execute the extend_expiration_dates command with the specified arguments.
|
||||
"""
|
||||
with patch(
|
||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||
return_value=True,
|
||||
):
|
||||
call_command("extend_expiration_dates")
|
||||
|
||||
def test_extends_expiration_date_correctly(self):
|
||||
"""
|
||||
Tests that the extend_expiration_dates method extends dates as expected
|
||||
"""
|
||||
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
||||
desired_domain.expiration_date = datetime.date(2024, 11, 15)
|
||||
|
||||
# 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(current_domain.expiration_date, datetime.date(2024, 11, 15))
|
||||
|
||||
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.expiration_date = datetime.date(2022, 5, 25)
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_extend_expiration_dates()
|
||||
|
||||
current_domain = Domain.objects.filter(name="fake.gov").get()
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||
# 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(2022, 5, 25))
|
||||
|
||||
def test_extends_expiration_date_skips_maximum_date(self):
|
||||
"""
|
||||
Tests that the extend_expiration_dates method correctly skips domains
|
||||
with an expiration date more than a certain threshold.
|
||||
"""
|
||||
desired_domain = Domain.objects.filter(name="fakemaximum.gov").get()
|
||||
desired_domain.expiration_date = datetime.date(2024, 12, 31)
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_extend_expiration_dates()
|
||||
|
||||
current_domain = Domain.objects.filter(name="fakemaximum.gov").get()
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||
# 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(2024, 12, 31))
|
||||
|
||||
def test_extends_expiration_date_skips_non_ready(self):
|
||||
"""
|
||||
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 = datetime.date(2023, 11, 15)
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_extend_expiration_dates()
|
||||
|
||||
current_domain = Domain.objects.filter(name="fakeneeded.gov").get()
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||
# 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 = datetime.date(2024, 11, 15)
|
||||
|
||||
# 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))
|
|
@ -268,14 +268,12 @@ class TestDomainApplication(TestCase):
|
|||
(self.ineligible_application, TransitionNotAllowed),
|
||||
]
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||
with less_console_noise():
|
||||
for application, exception_type in test_cases:
|
||||
with self.subTest(application=application, exception_type=exception_type):
|
||||
try:
|
||||
application.action_needed()
|
||||
except TransitionNotAllowed:
|
||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||
for application, exception_type in test_cases:
|
||||
with self.subTest(application=application, exception_type=exception_type):
|
||||
try:
|
||||
application.action_needed()
|
||||
except TransitionNotAllowed:
|
||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||
|
||||
def test_action_needed_transition_not_allowed(self):
|
||||
"""
|
||||
|
@ -288,12 +286,10 @@ class TestDomainApplication(TestCase):
|
|||
(self.withdrawn_application, TransitionNotAllowed),
|
||||
]
|
||||
|
||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||
with less_console_noise():
|
||||
for application, exception_type in test_cases:
|
||||
with self.subTest(application=application, exception_type=exception_type):
|
||||
with self.assertRaises(exception_type):
|
||||
application.action_needed()
|
||||
for application, exception_type in test_cases:
|
||||
with self.subTest(application=application, exception_type=exception_type):
|
||||
with self.assertRaises(exception_type):
|
||||
application.action_needed()
|
||||
|
||||
def test_approved_transition_allowed(self):
|
||||
"""
|
||||
|
|
|
@ -4,8 +4,10 @@ from django.test import Client, RequestFactory, TestCase
|
|||
from io import StringIO
|
||||
from registrar.models.domain_information import DomainInformation
|
||||
from registrar.models.domain import Domain
|
||||
from registrar.models.public_contact import PublicContact
|
||||
from registrar.models.user import User
|
||||
from django.contrib.auth import get_user_model
|
||||
from registrar.tests.common import MockEppLib
|
||||
from registrar.utility.csv_export import (
|
||||
write_header,
|
||||
write_body,
|
||||
|
@ -221,8 +223,9 @@ class CsvReportsTest(TestCase):
|
|||
self.assertEqual(expected_file_content, response.content)
|
||||
|
||||
|
||||
class ExportDataTest(TestCase):
|
||||
class ExportDataTest(MockEppLib):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
username = "test_user"
|
||||
first_name = "First"
|
||||
last_name = "Last"
|
||||
|
@ -327,11 +330,85 @@ class ExportDataTest(TestCase):
|
|||
)
|
||||
|
||||
def tearDown(self):
|
||||
PublicContact.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
DomainInformation.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
super().tearDown()
|
||||
|
||||
def test_export_domains_to_writer_security_emails(self):
|
||||
"""Test that export_domains_to_writer returns the
|
||||
expected security email"""
|
||||
|
||||
# Add security email information
|
||||
self.domain_1.name = "defaultsecurity.gov"
|
||||
self.domain_1.save()
|
||||
|
||||
# Invoke setter
|
||||
self.domain_1.security_contact
|
||||
|
||||
# Invoke setter
|
||||
self.domain_2.security_contact
|
||||
|
||||
# Invoke setter
|
||||
self.domain_3.security_contact
|
||||
|
||||
# Create a CSV file in memory
|
||||
csv_file = StringIO()
|
||||
writer = csv.writer(csv_file)
|
||||
|
||||
# Define columns, sort fields, and filter condition
|
||||
columns = [
|
||||
"Domain name",
|
||||
"Domain type",
|
||||
"Agency",
|
||||
"Organization name",
|
||||
"City",
|
||||
"State",
|
||||
"AO",
|
||||
"AO email",
|
||||
"Security contact email",
|
||||
"Status",
|
||||
"Expiration date",
|
||||
]
|
||||
sort_fields = ["domain__name"]
|
||||
filter_condition = {
|
||||
"domain__state__in": [
|
||||
Domain.State.READY,
|
||||
Domain.State.DNS_NEEDED,
|
||||
Domain.State.ON_HOLD,
|
||||
],
|
||||
}
|
||||
|
||||
self.maxDiff = None
|
||||
# Call the export functions
|
||||
write_header(writer, columns)
|
||||
write_body(writer, columns, sort_fields, filter_condition)
|
||||
|
||||
# Reset the CSV file's position to the beginning
|
||||
csv_file.seek(0)
|
||||
|
||||
# Read the content into a variable
|
||||
csv_content = csv_file.read()
|
||||
|
||||
# We expect READY domains,
|
||||
# sorted alphabetially by domain name
|
||||
expected_content = (
|
||||
"Domain name,Domain type,Agency,Organization name,City,State,AO,"
|
||||
"AO email,Security contact email,Status,Expiration date\n"
|
||||
"adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n"
|
||||
"adomain2.gov,Interstate,(blank),Dns needed\n"
|
||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n"
|
||||
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,dotgov@cisa.dhs.gov,Ready"
|
||||
)
|
||||
|
||||
# Normalize line endings and remove commas,
|
||||
# spaces and leading/trailing whitespace
|
||||
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||
|
||||
self.assertEqual(csv_content, expected_content)
|
||||
|
||||
def test_write_body(self):
|
||||
"""Test that write_body returns the
|
||||
existing domain, test that sort by domain name works,
|
||||
|
|
|
@ -18,178 +18,10 @@ from unittest.mock import patch
|
|||
|
||||
from registrar.models.contact import Contact
|
||||
|
||||
from .common import MockEppLib, MockSESClient, less_console_noise
|
||||
from .common import MockSESClient, less_console_noise
|
||||
import boto3_mocking # type: ignore
|
||||
|
||||
|
||||
class TestExtendExpirationDates(MockEppLib):
|
||||
def setUp(self):
|
||||
"""Defines the file name of migration_json and the folder its contained in"""
|
||||
super().setUp()
|
||||
# Create a valid domain that is updatable
|
||||
Domain.objects.get_or_create(
|
||||
name="waterbutpurple.gov", state=Domain.State.READY, expiration_date=datetime.date(2023, 11, 15)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="testytester@mail.com",
|
||||
domain_name="waterbutpurple.gov",
|
||||
epp_expiration_date=datetime.date(2023, 11, 15),
|
||||
)
|
||||
# Create a domain with an invalid expiration date
|
||||
Domain.objects.get_or_create(
|
||||
name="fake.gov", state=Domain.State.READY, expiration_date=datetime.date(2022, 5, 25)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="themoonisactuallycheese@mail.com",
|
||||
domain_name="fake.gov",
|
||||
epp_expiration_date=datetime.date(2022, 5, 25),
|
||||
)
|
||||
# Create a domain with an invalid state
|
||||
Domain.objects.get_or_create(
|
||||
name="fakeneeded.gov", state=Domain.State.DNS_NEEDED, expiration_date=datetime.date(2023, 11, 15)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="fakeneeded@mail.com",
|
||||
domain_name="fakeneeded.gov",
|
||||
epp_expiration_date=datetime.date(2023, 11, 15),
|
||||
)
|
||||
# Create a domain with a date greater than the maximum
|
||||
Domain.objects.get_or_create(
|
||||
name="fakemaximum.gov", state=Domain.State.READY, expiration_date=datetime.date(2024, 12, 31)
|
||||
)
|
||||
TransitionDomain.objects.get_or_create(
|
||||
username="fakemaximum@mail.com",
|
||||
domain_name="fakemaximum.gov",
|
||||
epp_expiration_date=datetime.date(2024, 12, 31),
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
"""Deletes all DB objects related to migrations"""
|
||||
super().tearDown()
|
||||
# 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_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.
|
||||
"""
|
||||
with patch(
|
||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||
return_value=True,
|
||||
):
|
||||
call_command("extend_expiration_dates")
|
||||
|
||||
def test_extends_expiration_date_correctly(self):
|
||||
"""
|
||||
Tests that the extend_expiration_dates method extends dates as expected
|
||||
"""
|
||||
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
||||
desired_domain.expiration_date = datetime.date(2024, 11, 15)
|
||||
|
||||
# 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(current_domain.expiration_date, datetime.date(2024, 11, 15))
|
||||
|
||||
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.expiration_date = datetime.date(2022, 5, 25)
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_extend_expiration_dates()
|
||||
|
||||
current_domain = Domain.objects.filter(name="fake.gov").get()
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||
# 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(2022, 5, 25))
|
||||
|
||||
def test_extends_expiration_date_skips_maximum_date(self):
|
||||
"""
|
||||
Tests that the extend_expiration_dates method correctly skips domains
|
||||
with an expiration date more than a certain threshold.
|
||||
"""
|
||||
desired_domain = Domain.objects.filter(name="fakemaximum.gov").get()
|
||||
desired_domain.expiration_date = datetime.date(2024, 12, 31)
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_extend_expiration_dates()
|
||||
|
||||
current_domain = Domain.objects.filter(name="fakemaximum.gov").get()
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||
# 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(2024, 12, 31))
|
||||
|
||||
def test_extends_expiration_date_skips_non_ready(self):
|
||||
"""
|
||||
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 = datetime.date(2023, 11, 15)
|
||||
|
||||
# Run the expiration date script
|
||||
self.run_extend_expiration_dates()
|
||||
|
||||
current_domain = Domain.objects.filter(name="fakeneeded.gov").get()
|
||||
self.assertEqual(desired_domain, current_domain)
|
||||
|
||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||
# 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 = datetime.date(2024, 11, 15)
|
||||
|
||||
# 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 TestProcessedMigrations(TestCase):
|
||||
"""This test case class is designed to verify the idempotency of migrations
|
||||
related to domain transitions in the application."""
|
||||
|
|
|
@ -36,8 +36,13 @@ from registrar.models import (
|
|||
User,
|
||||
)
|
||||
from registrar.views.application import ApplicationWizard, Step
|
||||
from datetime import date, datetime, timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
from .common import less_console_noise
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestViews(TestCase):
|
||||
|
@ -55,9 +60,9 @@ class TestViews(TestCase):
|
|||
|
||||
def test_application_form_not_logged_in(self):
|
||||
"""Application form not accessible without a logged-in user."""
|
||||
response = self.client.get("/register/")
|
||||
response = self.client.get("/request/")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn("/login?next=/register/", response.headers["Location"])
|
||||
self.assertIn("/login?next=/request/", response.headers["Location"])
|
||||
|
||||
|
||||
class TestWithUser(MockEppLib):
|
||||
|
@ -95,20 +100,8 @@ class LoggedInTests(TestWithUser):
|
|||
# clean up
|
||||
application.delete()
|
||||
|
||||
def test_home_lists_domains(self):
|
||||
response = self.client.get("/")
|
||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
self.assertNotContains(response, "igorville.gov")
|
||||
role, _ = UserDomainRole.objects.get_or_create(user=self.user, domain=domain, role=UserDomainRole.Roles.MANAGER)
|
||||
response = self.client.get("/")
|
||||
# count = 2 because it is also in screenreader content
|
||||
self.assertContains(response, "igorville.gov", count=2)
|
||||
self.assertContains(response, "Expired")
|
||||
# clean up
|
||||
role.delete()
|
||||
|
||||
def test_application_form_view(self):
|
||||
response = self.client.get("/register/", follow=True)
|
||||
response = self.client.get("/request/", follow=True)
|
||||
self.assertContains(
|
||||
response,
|
||||
"You’re about to start your .gov domain request.",
|
||||
|
@ -122,7 +115,7 @@ class LoggedInTests(TestWithUser):
|
|||
self.user.save()
|
||||
|
||||
with less_console_noise():
|
||||
response = self.client.get("/register/", follow=True)
|
||||
response = self.client.get("/request/", follow=True)
|
||||
print(response.status_code)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
@ -156,7 +149,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
self.assertEqual(detail_page.status_code, 302)
|
||||
# You can access the 'Location' header to get the redirect URL
|
||||
redirect_url = detail_page.url
|
||||
self.assertEqual(redirect_url, "/register/organization_type/")
|
||||
self.assertEqual(redirect_url, "/request/organization_type/")
|
||||
|
||||
def test_application_form_empty_submit(self):
|
||||
"""Tests empty submit on the first page after the acknowledgement page"""
|
||||
|
@ -250,7 +243,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(type_result.status_code, 302)
|
||||
self.assertEqual(type_result["Location"], "/register/organization_federal/")
|
||||
self.assertEqual(type_result["Location"], "/request/organization_federal/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- FEDERAL BRANCH PAGE ----
|
||||
|
@ -270,7 +263,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(federal_result.status_code, 302)
|
||||
self.assertEqual(federal_result["Location"], "/register/organization_contact/")
|
||||
self.assertEqual(federal_result["Location"], "/request/organization_contact/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- ORG CONTACT PAGE ----
|
||||
|
@ -303,7 +296,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(org_contact_result.status_code, 302)
|
||||
self.assertEqual(org_contact_result["Location"], "/register/authorizing_official/")
|
||||
self.assertEqual(org_contact_result["Location"], "/request/authorizing_official/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- AUTHORIZING OFFICIAL PAGE ----
|
||||
|
@ -328,7 +321,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(ao_result.status_code, 302)
|
||||
self.assertEqual(ao_result["Location"], "/register/current_sites/")
|
||||
self.assertEqual(ao_result["Location"], "/request/current_sites/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- CURRENT SITES PAGE ----
|
||||
|
@ -350,7 +343,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(current_sites_result.status_code, 302)
|
||||
self.assertEqual(current_sites_result["Location"], "/register/dotgov_domain/")
|
||||
self.assertEqual(current_sites_result["Location"], "/request/dotgov_domain/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- DOTGOV DOMAIN PAGE ----
|
||||
|
@ -370,7 +363,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(dotgov_result.status_code, 302)
|
||||
self.assertEqual(dotgov_result["Location"], "/register/purpose/")
|
||||
self.assertEqual(dotgov_result["Location"], "/request/purpose/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- PURPOSE PAGE ----
|
||||
|
@ -389,7 +382,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(purpose_result.status_code, 302)
|
||||
self.assertEqual(purpose_result["Location"], "/register/your_contact/")
|
||||
self.assertEqual(purpose_result["Location"], "/request/your_contact/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- YOUR CONTACT INFO PAGE ----
|
||||
|
@ -417,7 +410,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(your_contact_result.status_code, 302)
|
||||
self.assertEqual(your_contact_result["Location"], "/register/other_contacts/")
|
||||
self.assertEqual(your_contact_result["Location"], "/request/other_contacts/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- OTHER CONTACTS PAGE ----
|
||||
|
@ -455,7 +448,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(other_contacts_result.status_code, 302)
|
||||
self.assertEqual(other_contacts_result["Location"], "/register/anything_else/")
|
||||
self.assertEqual(other_contacts_result["Location"], "/request/anything_else/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- ANYTHING ELSE PAGE ----
|
||||
|
@ -475,7 +468,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(anything_else_result.status_code, 302)
|
||||
self.assertEqual(anything_else_result["Location"], "/register/requirements/")
|
||||
self.assertEqual(anything_else_result["Location"], "/request/requirements/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- REQUIREMENTS PAGE ----
|
||||
|
@ -495,7 +488,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEqual(requirements_result.status_code, 302)
|
||||
self.assertEqual(requirements_result["Location"], "/register/review/")
|
||||
self.assertEqual(requirements_result["Location"], "/request/review/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# ---- REVIEW AND FINSIHED PAGES ----
|
||||
|
@ -549,7 +542,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
review_result = review_form.submit()
|
||||
|
||||
self.assertEqual(review_result.status_code, 302)
|
||||
self.assertEqual(review_result["Location"], "/register/finished/")
|
||||
self.assertEqual(review_result["Location"], "/request/finished/")
|
||||
num_pages_tested += 1
|
||||
|
||||
# following this redirect is a GET request, so include the cookie
|
||||
|
@ -630,7 +623,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the federal branch
|
||||
# question
|
||||
self.assertEqual(type_result.status_code, 302)
|
||||
self.assertEqual(type_result["Location"], "/register/organization_federal/")
|
||||
self.assertEqual(type_result["Location"], "/request/organization_federal/")
|
||||
|
||||
# and the step label should appear in the sidebar of the resulting page
|
||||
# but the step label for the elections page should not appear
|
||||
|
@ -647,7 +640,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the contact
|
||||
# question
|
||||
self.assertEqual(federal_result.status_code, 302)
|
||||
self.assertEqual(federal_result["Location"], "/register/organization_contact/")
|
||||
self.assertEqual(federal_result["Location"], "/request/organization_contact/")
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
contact_page = federal_result.follow()
|
||||
self.assertContains(contact_page, "Federal agency")
|
||||
|
@ -684,7 +677,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
|
||||
# the post request should return a redirect to the elections question
|
||||
self.assertEqual(type_result.status_code, 302)
|
||||
self.assertEqual(type_result["Location"], "/register/organization_election/")
|
||||
self.assertEqual(type_result["Location"], "/request/organization_election/")
|
||||
|
||||
# and the step label should appear in the sidebar of the resulting page
|
||||
# but the step label for the elections page should not appear
|
||||
|
@ -701,7 +694,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the contact
|
||||
# question
|
||||
self.assertEqual(election_result.status_code, 302)
|
||||
self.assertEqual(election_result["Location"], "/register/organization_contact/")
|
||||
self.assertEqual(election_result["Location"], "/request/organization_contact/")
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
contact_page = election_result.follow()
|
||||
self.assertNotContains(contact_page, "Federal agency")
|
||||
|
@ -739,7 +732,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
|
||||
# Should be a link to the organization_federal page
|
||||
self.assertGreater(
|
||||
len(new_page.html.find_all("a", href="/register/organization_federal/")),
|
||||
len(new_page.html.find_all("a", href="/request/organization_federal/")),
|
||||
0,
|
||||
)
|
||||
|
||||
|
@ -786,7 +779,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
|
|||
# the post request should return a redirect to the
|
||||
# about your organization page if it was successful.
|
||||
self.assertEqual(contact_result.status_code, 302)
|
||||
self.assertEqual(contact_result["Location"], "/register/about_your_organization/")
|
||||
self.assertEqual(contact_result["Location"], "/request/about_your_organization/")
|
||||
|
||||
def test_application_about_your_organization_special(self):
|
||||
"""Special districts have to answer an additional question."""
|
||||
|
@ -2186,8 +2179,20 @@ class TestWithDomainPermissions(TestWithUser):
|
|||
self.domain_with_ip, _ = Domain.objects.get_or_create(name="nameserverwithip.gov")
|
||||
self.domain_just_nameserver, _ = Domain.objects.get_or_create(name="justnameserver.com")
|
||||
self.domain_no_information, _ = Domain.objects.get_or_create(name="noinformation.gov")
|
||||
self.domain_on_hold, _ = Domain.objects.get_or_create(name="on-hold.gov", state=Domain.State.ON_HOLD)
|
||||
self.domain_deleted, _ = Domain.objects.get_or_create(name="deleted.gov", state=Domain.State.DELETED)
|
||||
self.domain_on_hold, _ = Domain.objects.get_or_create(
|
||||
name="on-hold.gov",
|
||||
state=Domain.State.ON_HOLD,
|
||||
expiration_date=timezone.make_aware(
|
||||
datetime.combine(date.today() + timedelta(days=1), datetime.min.time())
|
||||
),
|
||||
)
|
||||
self.domain_deleted, _ = Domain.objects.get_or_create(
|
||||
name="deleted.gov",
|
||||
state=Domain.State.DELETED,
|
||||
expiration_date=timezone.make_aware(
|
||||
datetime.combine(date.today() + timedelta(days=1), datetime.min.time())
|
||||
),
|
||||
)
|
||||
|
||||
self.domain_dsdata, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||
self.domain_multdsdata, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
|
||||
|
@ -2327,14 +2332,59 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
|
||||
|
||||
class TestDomainDetail(TestDomainOverview):
|
||||
@skip("Assertion broke for no reason, why? Need to fix")
|
||||
def test_domain_detail_link_works(self):
|
||||
home_page = self.app.get("/")
|
||||
logger.info(f"This is the value of home_page: {home_page}")
|
||||
self.assertContains(home_page, "igorville.gov")
|
||||
# click the "Edit" link
|
||||
detail_page = home_page.click("Manage", index=0)
|
||||
self.assertContains(detail_page, "igorville.gov")
|
||||
self.assertContains(detail_page, "Status")
|
||||
|
||||
def test_unknown_domain_does_not_show_as_expired_on_homepage(self):
|
||||
"""An UNKNOWN domain does not show as expired on the homepage.
|
||||
It shows as 'DNS needed'"""
|
||||
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
||||
# from constructors. Let's reset.
|
||||
Domain.objects.all().delete()
|
||||
UserDomainRole.objects.all().delete()
|
||||
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
home_page = self.app.get("/")
|
||||
self.assertNotContains(home_page, "igorville.gov")
|
||||
self.role, _ = UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "igorville.gov")
|
||||
igorville = Domain.objects.get(name="igorville.gov")
|
||||
self.assertEquals(igorville.state, Domain.State.UNKNOWN)
|
||||
self.assertNotContains(home_page, "Expired")
|
||||
self.assertContains(home_page, "DNS needed")
|
||||
|
||||
def test_unknown_domain_does_not_show_as_expired_on_detail_page(self):
|
||||
"""An UNKNOWN domain does not show as expired on the detail page.
|
||||
It shows as 'DNS needed'"""
|
||||
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
||||
# from constructors. Let's reset.
|
||||
Domain.objects.all().delete()
|
||||
UserDomainRole.objects.all().delete()
|
||||
|
||||
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain)
|
||||
self.role, _ = UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "igorville.gov")
|
||||
igorville = Domain.objects.get(name="igorville.gov")
|
||||
self.assertEquals(igorville.state, Domain.State.UNKNOWN)
|
||||
detail_page = home_page.click("Manage", index=0)
|
||||
self.assertNotContains(detail_page, "Expired")
|
||||
|
||||
self.assertContains(detail_page, "DNS needed")
|
||||
|
||||
def test_domain_detail_blocked_for_ineligible_user(self):
|
||||
"""We could easily duplicate this test for all domain management
|
||||
views, but a single url test should be solid enough since all domain
|
||||
|
|
|
@ -26,12 +26,23 @@ def get_domain_infos(filter_condition, sort_fields):
|
|||
|
||||
def write_row(writer, columns, domain_info):
|
||||
security_contacts = domain_info.domain.contacts.filter(contact_type=PublicContact.ContactTypeChoices.SECURITY)
|
||||
|
||||
# For linter
|
||||
ao = " "
|
||||
if domain_info.authorizing_official:
|
||||
first_name = domain_info.authorizing_official.first_name or ""
|
||||
last_name = domain_info.authorizing_official.last_name or ""
|
||||
ao = first_name + " " + last_name
|
||||
|
||||
security_email = " "
|
||||
if security_contacts:
|
||||
security_email = security_contacts[0].email
|
||||
|
||||
invalid_emails = {"registrar@dotgov.gov"}
|
||||
# These are default emails that should not be displayed in the csv report
|
||||
if security_email is not None and security_email.lower() in invalid_emails:
|
||||
security_email = "(blank)"
|
||||
|
||||
# create a dictionary of fields which can be included in output
|
||||
FIELDS = {
|
||||
"Domain name": domain_info.domain.name,
|
||||
|
@ -44,13 +55,14 @@ def write_row(writer, columns, domain_info):
|
|||
"State": domain_info.state_territory,
|
||||
"AO": ao,
|
||||
"AO email": domain_info.authorizing_official.email if domain_info.authorizing_official else " ",
|
||||
"Security contact email": security_contacts[0].email if security_contacts else " ",
|
||||
"Security contact email": security_email,
|
||||
"Status": domain_info.domain.get_state_display(),
|
||||
"Expiration date": domain_info.domain.expiration_date,
|
||||
"Created at": domain_info.domain.created_at,
|
||||
"First ready": domain_info.domain.first_ready,
|
||||
"Deleted": domain_info.domain.deleted,
|
||||
}
|
||||
|
||||
writer.writerow([FIELDS.get(column, "") for column in columns])
|
||||
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
|||
URL_NAMESPACE = "application"
|
||||
# name for accessing /application/<id>/edit
|
||||
EDIT_URL_NAME = "edit-application"
|
||||
NEW_URL_NAME = "/register/"
|
||||
NEW_URL_NAME = "/request/"
|
||||
# We need to pass our human-readable step titles as context to the templates.
|
||||
TITLES = {
|
||||
Step.ORGANIZATION_TYPE: _("Type of organization"),
|
||||
|
@ -150,6 +150,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
|||
def storage(self):
|
||||
# marking session as modified on every access
|
||||
# so that updates to nested keys are always saved
|
||||
# push to sandbox will remove
|
||||
self.request.session.modified = True
|
||||
return self.request.session.setdefault(self.prefix, {})
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue