Merge branch 'main' into gd/2354-handle-portfolio-edit-mode

This commit is contained in:
zandercymatics 2024-07-30 08:58:49 -06:00
commit 82c9861dd5
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
31 changed files with 4778 additions and 3738 deletions

View file

@ -3,10 +3,10 @@ from dateutil.tz import tzlocal # type: ignore
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from pathlib import Path from pathlib import Path
from django.test import TestCase from django.test import TestCase
from api.tests.common import less_console_noise_decorator
from gevent.exceptions import ConcurrentObjectUseError from gevent.exceptions import ConcurrentObjectUseError
from epplibwrapper.client import EPPLibWrapper from epplibwrapper.client import EPPLibWrapper
from epplibwrapper.errors import RegistryError, LoginError from epplibwrapper.errors import RegistryError, LoginError
from .common import less_console_noise
import logging import logging
try: try:
@ -24,99 +24,101 @@ logger = logging.getLogger(__name__)
class TestClient(TestCase): class TestClient(TestCase):
"""Test the EPPlibwrapper client""" """Test the EPPlibwrapper client"""
@less_console_noise_decorator
def fake_result(self, code, msg): def fake_result(self, code, msg):
"""Helper function to create a fake Result object""" """Helper function to create a fake Result object"""
return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id") return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id")
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_initialize_client_success(self, mock_client): def test_initialize_client_success(self, mock_client):
"""Test when the initialize_client is successful""" """Test when the initialize_client is successful"""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods mock_connect = MagicMock()
mock_connect = MagicMock() # Create a mock Result instance
# Create a mock Result instance mock_result = MagicMock(spec=Result)
mock_result = MagicMock(spec=Result) mock_result.code = 200
mock_result.code = 200 mock_result.msg = "Success"
mock_result.msg = "Success" mock_result.res_data = ["data1", "data2"]
mock_result.res_data = ["data1", "data2"] mock_result.cl_tr_id = "client_id"
mock_result.cl_tr_id = "client_id" mock_result.sv_tr_id = "server_id"
mock_result.sv_tr_id = "server_id" mock_send = MagicMock(return_value=mock_result)
mock_send = MagicMock(return_value=mock_result) mock_client.return_value.connect = mock_connect
mock_client.return_value.connect = mock_connect mock_client.return_value.send = mock_send
mock_client.return_value.send = mock_send
# Create EPPLibWrapper instance and initialize client # Create EPPLibWrapper instance and initialize client
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
# Assert that connect method is called once # Assert that connect method is called once
mock_connect.assert_called_once() mock_connect.assert_called_once()
# Assert that _client is not None after initialization # Assert that _client is not None after initialization
self.assertIsNotNone(wrapper._client) self.assertIsNotNone(wrapper._client)
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_initialize_client_transport_error(self, mock_client): def test_initialize_client_transport_error(self, mock_client):
"""Test when the send(login) step of initialize_client raises a TransportError.""" """Test when the send(login) step of initialize_client raises a TransportError."""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods mock_connect = MagicMock()
mock_connect = MagicMock() mock_send = MagicMock(side_effect=TransportError("Transport error"))
mock_send = MagicMock(side_effect=TransportError("Transport error")) mock_client.return_value.connect = mock_connect
mock_client.return_value.connect = mock_connect mock_client.return_value.send = mock_send
mock_client.return_value.send = mock_send
with self.assertRaises(RegistryError): with self.assertRaises(RegistryError):
# Create EPPLibWrapper instance and initialize client # Create EPPLibWrapper instance and initialize client
# if functioning as expected, initial __init__ should except # if functioning as expected, initial __init__ should except
# and log any Exception raised # and log any Exception raised
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
# so call _initialize_client a second time directly to test # so call _initialize_client a second time directly to test
# the raised exception # the raised exception
wrapper._initialize_client() wrapper._initialize_client()
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_initialize_client_login_error(self, mock_client): def test_initialize_client_login_error(self, mock_client):
"""Test when the send(login) step of initialize_client returns (2400) comamnd failed code.""" """Test when the send(login) step of initialize_client returns (2400) comamnd failed code."""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods mock_connect = MagicMock()
mock_connect = MagicMock() # Create a mock Result instance
# Create a mock Result instance mock_result = MagicMock(spec=Result)
mock_result = MagicMock(spec=Result) mock_result.code = 2400
mock_result.code = 2400 mock_result.msg = "Login failed"
mock_result.msg = "Login failed" mock_result.res_data = ["data1", "data2"]
mock_result.res_data = ["data1", "data2"] mock_result.cl_tr_id = "client_id"
mock_result.cl_tr_id = "client_id" mock_result.sv_tr_id = "server_id"
mock_result.sv_tr_id = "server_id" mock_send = MagicMock(return_value=mock_result)
mock_send = MagicMock(return_value=mock_result) mock_client.return_value.connect = mock_connect
mock_client.return_value.connect = mock_connect mock_client.return_value.send = mock_send
mock_client.return_value.send = mock_send
with self.assertRaises(LoginError): with self.assertRaises(LoginError):
# Create EPPLibWrapper instance and initialize client # Create EPPLibWrapper instance and initialize client
# if functioning as expected, initial __init__ should except # if functioning as expected, initial __init__ should except
# and log any Exception raised # and log any Exception raised
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
# so call _initialize_client a second time directly to test # so call _initialize_client a second time directly to test
# the raised exception # the raised exception
wrapper._initialize_client() wrapper._initialize_client()
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_initialize_client_unknown_exception(self, mock_client): def test_initialize_client_unknown_exception(self, mock_client):
"""Test when the send(login) step of initialize_client raises an unexpected Exception.""" """Test when the send(login) step of initialize_client raises an unexpected Exception."""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods mock_connect = MagicMock()
mock_connect = MagicMock() mock_send = MagicMock(side_effect=Exception("Unknown exception"))
mock_send = MagicMock(side_effect=Exception("Unknown exception")) mock_client.return_value.connect = mock_connect
mock_client.return_value.connect = mock_connect mock_client.return_value.send = mock_send
mock_client.return_value.send = mock_send
with self.assertRaises(RegistryError): with self.assertRaises(RegistryError):
# Create EPPLibWrapper instance and initialize client # Create EPPLibWrapper instance and initialize client
# if functioning as expected, initial __init__ should except # if functioning as expected, initial __init__ should except
# and log any Exception raised # and log any Exception raised
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
# so call _initialize_client a second time directly to test # so call _initialize_client a second time directly to test
# the raised exception # the raised exception
wrapper._initialize_client() wrapper._initialize_client()
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_initialize_client_fails_recovers_with_send_command(self, mock_client): def test_initialize_client_fails_recovers_with_send_command(self, mock_client):
"""Test when the initialize_client fails on the connect() step. And then a subsequent """Test when the initialize_client fails on the connect() step. And then a subsequent
@ -126,56 +128,56 @@ class TestClient(TestCase):
Initialization step fails at app init Initialization step fails at app init
Send command fails (with 2400 code) prompting retry Send command fails (with 2400 code) prompting retry
Client closes and re-initializes, and command is sent successfully""" Client closes and re-initializes, and command is sent successfully"""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods # close() should return successfully
# close() should return successfully mock_close = MagicMock()
mock_close = MagicMock() mock_client.return_value.close = mock_close
mock_client.return_value.close = mock_close # Create success and failure results
# Create success and failure results command_success_result = self.fake_result(1000, "Command completed successfully")
command_success_result = self.fake_result(1000, "Command completed successfully") command_failure_result = self.fake_result(2400, "Command failed")
command_failure_result = self.fake_result(2400, "Command failed") # side_effect for the connect() calls
# side_effect for the connect() calls # first connect() should raise an Exception
# first connect() should raise an Exception # subsequent connect() calls should return success
# subsequent connect() calls should return success connect_call_count = 0
connect_call_count = 0
def connect_side_effect(*args, **kwargs): def connect_side_effect(*args, **kwargs):
nonlocal connect_call_count nonlocal connect_call_count
connect_call_count += 1 connect_call_count += 1
if connect_call_count == 1: if connect_call_count == 1:
raise Exception("Connection failed") raise Exception("Connection failed")
else: else:
return command_success_result return command_success_result
mock_connect = MagicMock(side_effect=connect_side_effect) mock_connect = MagicMock(side_effect=connect_side_effect)
mock_client.return_value.connect = mock_connect mock_client.return_value.connect = mock_connect
# side_effect for the send() calls # side_effect for the send() calls
# first send will be the send("InfoDomainCommand") and should fail # first send will be the send("InfoDomainCommand") and should fail
# subsequend send() calls should return success # subsequend send() calls should return success
send_call_count = 0 send_call_count = 0
def send_side_effect(*args, **kwargs): def send_side_effect(*args, **kwargs):
nonlocal send_call_count nonlocal send_call_count
send_call_count += 1 send_call_count += 1
if send_call_count == 1: if send_call_count == 1:
return command_failure_result return command_failure_result
else: else:
return command_success_result return command_success_result
mock_send = MagicMock(side_effect=send_side_effect) mock_send = MagicMock(side_effect=send_side_effect)
mock_client.return_value.send = mock_send mock_client.return_value.send = mock_send
# Create EPPLibWrapper instance and call send command # Create EPPLibWrapper instance and call send command
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
wrapper.send("InfoDomainCommand", cleaned=True) wrapper.send("InfoDomainCommand", cleaned=True)
# two connect() calls should be made, the initial failed connect() # two connect() calls should be made, the initial failed connect()
# and the successful connect() during retry() # and the successful connect() during retry()
self.assertEquals(mock_connect.call_count, 2) self.assertEquals(mock_connect.call_count, 2)
# close() should only be called once, during retry() # close() should only be called once, during retry()
mock_close.assert_called_once() mock_close.assert_called_once()
# send called 4 times: failed send("InfoDomainCommand"), passed send(logout), # send called 4 times: failed send("InfoDomainCommand"), passed send(logout),
# passed send(login), passed send("InfoDomainCommand") # passed send(login), passed send("InfoDomainCommand")
self.assertEquals(mock_send.call_count, 4) self.assertEquals(mock_send.call_count, 4)
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_send_command_failed_retries_and_fails_again(self, mock_client): def test_send_command_failed_retries_and_fails_again(self, mock_client):
"""Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry """Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry
@ -185,42 +187,42 @@ class TestClient(TestCase):
Initialization succeeds Initialization succeeds
Send command fails (with 2400 code) prompting retry Send command fails (with 2400 code) prompting retry
Client closes and re-initializes, and command fails again with 2400""" Client closes and re-initializes, and command fails again with 2400"""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods # connect() and close() should succeed throughout
# connect() and close() should succeed throughout mock_connect = MagicMock()
mock_connect = MagicMock() mock_close = MagicMock()
mock_close = MagicMock() # Create a mock Result instance
# Create a mock Result instance send_command_success_result = self.fake_result(1000, "Command completed successfully")
send_command_success_result = self.fake_result(1000, "Command completed successfully") send_command_failure_result = self.fake_result(2400, "Command failed")
send_command_failure_result = self.fake_result(2400, "Command failed")
# side_effect for send command, passes for all other sends (login, logout), but # side_effect for send command, passes for all other sends (login, logout), but
# fails for send("InfoDomainCommand") # fails for send("InfoDomainCommand")
def side_effect(*args, **kwargs): def side_effect(*args, **kwargs):
if args[0] == "InfoDomainCommand": if args[0] == "InfoDomainCommand":
return send_command_failure_result return send_command_failure_result
else: else:
return send_command_success_result return send_command_success_result
mock_send = MagicMock(side_effect=side_effect) mock_send = MagicMock(side_effect=side_effect)
mock_client.return_value.connect = mock_connect mock_client.return_value.connect = mock_connect
mock_client.return_value.close = mock_close mock_client.return_value.close = mock_close
mock_client.return_value.send = mock_send mock_client.return_value.send = mock_send
with self.assertRaises(RegistryError): with self.assertRaises(RegistryError):
# Create EPPLibWrapper instance and initialize client # Create EPPLibWrapper instance and initialize client
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
# call send, which should throw a RegistryError (after retry) # call send, which should throw a RegistryError (after retry)
wrapper.send("InfoDomainCommand", cleaned=True) wrapper.send("InfoDomainCommand", cleaned=True)
# connect() should be called twice, once during initialization, second time # connect() should be called twice, once during initialization, second time
# during retry # during retry
self.assertEquals(mock_connect.call_count, 2) self.assertEquals(mock_connect.call_count, 2)
# close() is called once during retry # close() is called once during retry
mock_close.assert_called_once() mock_close.assert_called_once()
# send() is called 5 times: send(login), send(command) fails, send(logout) # send() is called 5 times: send(login), send(command) fails, send(logout)
# send(login), send(command) # send(login), send(command)
self.assertEquals(mock_send.call_count, 5) self.assertEquals(mock_send.call_count, 5)
@less_console_noise_decorator
@patch("epplibwrapper.client.Client") @patch("epplibwrapper.client.Client")
def test_send_command_failure_prompts_successful_retry(self, mock_client): def test_send_command_failure_prompts_successful_retry(self, mock_client):
"""Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry """Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry
@ -229,40 +231,40 @@ class TestClient(TestCase):
Initialization succeeds Initialization succeeds
Send command fails (with 2400 code) prompting retry Send command fails (with 2400 code) prompting retry
Client closes and re-initializes, and command succeeds""" Client closes and re-initializes, and command succeeds"""
with less_console_noise(): # Mock the Client instance and its methods
# Mock the Client instance and its methods # connect() and close() should succeed throughout
# connect() and close() should succeed throughout mock_connect = MagicMock()
mock_connect = MagicMock() mock_close = MagicMock()
mock_close = MagicMock() # create success and failure result messages
# create success and failure result messages send_command_success_result = self.fake_result(1000, "Command completed successfully")
send_command_success_result = self.fake_result(1000, "Command completed successfully") send_command_failure_result = self.fake_result(2400, "Command failed")
send_command_failure_result = self.fake_result(2400, "Command failed") # side_effect for send call, initial send(login) succeeds during initialization, next send(command)
# side_effect for send call, initial send(login) succeeds during initialization, next send(command) # fails, subsequent sends (logout, login, command) all succeed
# fails, subsequent sends (logout, login, command) all succeed send_call_count = 0
send_call_count = 0
def side_effect(*args, **kwargs): def side_effect(*args, **kwargs):
nonlocal send_call_count nonlocal send_call_count
send_call_count += 1 send_call_count += 1
if send_call_count == 2: if send_call_count == 2:
return send_command_failure_result return send_command_failure_result
else: else:
return send_command_success_result return send_command_success_result
mock_send = MagicMock(side_effect=side_effect) mock_send = MagicMock(side_effect=side_effect)
mock_client.return_value.connect = mock_connect mock_client.return_value.connect = mock_connect
mock_client.return_value.close = mock_close mock_client.return_value.close = mock_close
mock_client.return_value.send = mock_send mock_client.return_value.send = mock_send
# Create EPPLibWrapper instance and initialize client # Create EPPLibWrapper instance and initialize client
wrapper = EPPLibWrapper() wrapper = EPPLibWrapper()
wrapper.send("InfoDomainCommand", cleaned=True) wrapper.send("InfoDomainCommand", cleaned=True)
# connect() is called twice, once during initialization of app, once during retry # connect() is called twice, once during initialization of app, once during retry
self.assertEquals(mock_connect.call_count, 2) self.assertEquals(mock_connect.call_count, 2)
# close() is called once, during retry # close() is called once, during retry
mock_close.assert_called_once() mock_close.assert_called_once()
# send() is called 5 times: send(login), send(command) fail, send(logout), send(login), send(command) # send() is called 5 times: send(login), send(command) fail, send(logout), send(login), send(command)
self.assertEquals(mock_send.call_count, 5) self.assertEquals(mock_send.call_count, 5)
@less_console_noise_decorator
def fake_failure_send_concurrent_threads(self, command=None, cleaned=None): def fake_failure_send_concurrent_threads(self, command=None, cleaned=None):
""" """
Raises a ConcurrentObjectUseError, which gevent throws when accessing Raises a ConcurrentObjectUseError, which gevent throws when accessing
@ -277,6 +279,7 @@ class TestClient(TestCase):
""" """
pass # noqa pass # noqa
@less_console_noise_decorator
def fake_success_send(self, command=None, cleaned=None): def fake_success_send(self, command=None, cleaned=None):
""" """
Simulates receiving a success response from EPP. Simulates receiving a success response from EPP.
@ -292,6 +295,7 @@ class TestClient(TestCase):
) )
return mock return mock
@less_console_noise_decorator
def fake_info_domain_received(self, command=None, cleaned=None): def fake_info_domain_received(self, command=None, cleaned=None):
""" """
Simulates receiving a response by reading from a predefined XML file. Simulates receiving a response by reading from a predefined XML file.
@ -300,6 +304,7 @@ class TestClient(TestCase):
xml = (location).read_bytes() xml = (location).read_bytes()
return xml return xml
@less_console_noise_decorator
def get_fake_epp_result(self): def get_fake_epp_result(self):
"""Mimics a return from EPP by returning a dictionary in the same format""" """Mimics a return from EPP by returning a dictionary in the same format"""
result = { result = {
@ -338,6 +343,7 @@ class TestClient(TestCase):
} }
return result return result
@less_console_noise_decorator
def test_send_command_close_failure_recovers(self): def test_send_command_close_failure_recovers(self):
""" """
Validates the resilience of the connection handling mechanism Validates the resilience of the connection handling mechanism
@ -350,7 +356,6 @@ class TestClient(TestCase):
- Subsequently, the client re-initializes the connection. - Subsequently, the client re-initializes the connection.
- A retry of the command execution post-reinitialization succeeds. - A retry of the command execution post-reinitialization succeeds.
""" """
expected_result = self.get_fake_epp_result() expected_result = self.get_fake_epp_result()
wrapper = None wrapper = None
# Trigger a retry # Trigger a retry

View file

@ -72,6 +72,29 @@ body {
} }
} }
.section--outlined__header--no-portfolio {
.section--outlined__search,
.section--outlined__utility-button {
margin-top: units(2);
}
@include at-media(tablet) {
display: flex;
column-gap: units(3);
.section--outlined__search,
.section--outlined__utility-button {
margin-top: 0;
}
.section--outlined__search {
flex-grow: 4;
// Align right
max-width: 383px;
margin-left: auto;
}
}
}
.break-word { .break-word {
word-break: break-word; word-break: break-word;
} }

View file

@ -205,3 +205,12 @@ a.usa-button--unstyled:visited {
} }
} }
.usa-icon.usa-icon--big {
margin: 0;
height: 1.5em;
width: 1.5em;
}
.margin-right-neg-4px {
margin-right: -4px;
}

View file

@ -9,7 +9,7 @@ from django.urls import include, path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from registrar import views from registrar import views
from registrar.views.admin_views import ( from registrar.views.report_views import (
ExportDataDomainsGrowth, ExportDataDomainsGrowth,
ExportDataFederal, ExportDataFederal,
ExportDataFull, ExportDataFull,
@ -19,6 +19,7 @@ from registrar.views.admin_views import (
ExportDataUnmanagedDomains, ExportDataUnmanagedDomains,
AnalyticsView, AnalyticsView,
ExportDomainRequestDataFull, ExportDomainRequestDataFull,
ExportDataTypeUser,
) )
from registrar.views.domain_request import Step from registrar.views.domain_request import Step
@ -124,6 +125,11 @@ urlpatterns = [
name="analytics", name="analytics",
), ),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path(
"reports/export_data_type_user/",
ExportDataTypeUser.as_view(),
name="export_data_type_user",
),
path( path(
"domain-request/<id>/edit/", "domain-request/<id>/edit/",
views.DomainRequestWizard.as_view(), views.DomainRequestWizard.as_view(),

View file

@ -10,3 +10,6 @@ from .domain import (
DomainDsdataFormset, DomainDsdataFormset,
DomainDsdataForm, DomainDsdataForm,
) )
from .portfolio import (
PortfolioOrgAddressForm,
)

View file

@ -458,7 +458,7 @@ class DomainOrgNameAddressForm(forms.ModelForm):
# the database fields have blank=True so ModelForm doesn't create # the database fields have blank=True so ModelForm doesn't create
# required fields by default. Use this list in __init__ to mark each # required fields by default. Use this list in __init__ to mark each
# of these fields as required # of these fields as required
required = ["organization_name", "address_line1", "city", "zipcode"] required = ["organization_name", "address_line1", "city", "state_territory", "zipcode"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View file

@ -0,0 +1,69 @@
"""Forms for portfolio."""
import logging
from django import forms
from django.core.validators import RegexValidator
from ..models import DomainInformation, Portfolio
logger = logging.getLogger(__name__)
class PortfolioOrgAddressForm(forms.ModelForm):
"""Form for updating the portfolio org mailing address."""
zipcode = forms.CharField(
label="Zip code",
validators=[
RegexValidator(
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
message="Enter a zip code in the required format, like 12345 or 12345-6789.",
)
],
)
class Meta:
model = Portfolio
fields = [
"address_line1",
"address_line2",
"city",
"state_territory",
"zipcode",
# "urbanization",
]
error_messages = {
"address_line1": {"required": "Enter the street address of your organization."},
"city": {"required": "Enter the city where your organization is located."},
"state_territory": {
"required": "Select the state, territory, or military post where your organization is located."
},
}
widgets = {
# We need to set the required attributed for State/territory
# because for this fields we are creating an individual
# instance of the Select. For the other fields we use the for loop to set
# the class's required attribute to true.
"address_line1": forms.TextInput,
"address_line2": forms.TextInput,
"city": forms.TextInput,
"state_territory": forms.Select(
attrs={
"required": True,
},
choices=DomainInformation.StateTerritoryChoices.choices,
),
# "urbanization": forms.TextInput,
}
# the database fields have blank=True so ModelForm doesn't create
# required fields by default. Use this list in __init__ to mark each
# of these fields as required
required = ["address_line1", "city", "state_territory", "zipcode"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name in self.required:
self.fields[field_name].required = True
self.fields["state_territory"].widget.attrs.pop("maxlength", None)
self.fields["zipcode"].widget.attrs.pop("maxlength", None)

View file

@ -6,11 +6,6 @@ from registrar.models.federal_agency import FederalAgency
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
# def get_default_federal_agency():
# """returns non-federal agency"""
# return FederalAgency.objects.filter(agency="Non-Federal Agency").first()
class Portfolio(TimeStampedModel): class Portfolio(TimeStampedModel):
""" """
Portfolio is used for organizing domains/domain-requests into Portfolio is used for organizing domains/domain-requests into

View file

@ -170,18 +170,11 @@ class CreateOrUpdateOrganizationTypeHelper:
# There is no avenue for this to occur in the UI, # There is no avenue for this to occur in the UI,
# as such - this can only occur if the object is initialized in this way. # as such - this can only occur if the object is initialized in this way.
# Or if there are pre-existing data. # Or if there are pre-existing data.
logger.debug(
"create_or_update_organization_type() -> is_election_board "
f"cannot exist for {generic_org_type}. Setting to None."
)
self.instance.is_election_board = None self.instance.is_election_board = None
self.instance.organization_type = generic_org_type self.instance.organization_type = generic_org_type
else: else:
# This can only happen with manual data tinkering, which causes these to be out of sync. # This can only happen with manual data tinkering, which causes these to be out of sync.
if self.instance.is_election_board is None: if self.instance.is_election_board is None:
logger.warning(
"create_or_update_organization_type() -> is_election_board is out of sync. Updating value."
)
self.instance.is_election_board = False self.instance.is_election_board = False
if self.instance.is_election_board: if self.instance.is_election_board:
@ -218,10 +211,6 @@ class CreateOrUpdateOrganizationTypeHelper:
# There is no avenue for this to occur in the UI, # There is no avenue for this to occur in the UI,
# as such - this can only occur if the object is initialized in this way. # as such - this can only occur if the object is initialized in this way.
# Or if there are pre-existing data. # Or if there are pre-existing data.
logger.warning(
"create_or_update_organization_type() -> is_election_board "
f"cannot exist for {current_org_type}. Setting to None."
)
self.instance.is_election_board = None self.instance.is_election_board = None
else: else:
# if self.instance.organization_type is set to None, then this means # if self.instance.organization_type is set to None, then this means

View file

@ -1,16 +1,14 @@
{% load static %} {% load static %}
<section class="section--outlined domains{% if portfolio is not None %} margin-top-0{% endif %}" id="domains"> <section class="section--outlined domains{% if portfolio is not None %} margin-top-0{% endif %}" id="domains">
<div class="grid-row"> <div class="section--outlined__header margin-bottom-3 {% if portfolio is None %} section--outlined__header--no-portfolio justify-content-space-between{% else %} grid-row{% endif %}">
<!-- Use portfolio_base_permission when merging into 2366 then delete this comment --> <!-- Use portfolio_base_permission when merging into 2366 then delete this comment -->
{% if portfolio is None %} {% if portfolio is None %}
<div class="mobile:grid-col-12 desktop:grid-col-6"> <h2 id="domains-header" class="display-inline-block">Domains</h2>
<h2 id="domains-header" class="flex-6">Domains</h2> <span class="display-none" id="no-portfolio-js-flag"></span>
</div>
<span class="display-none" id="no-portfolio-js-flag"></span>
{% endif %} {% endif %}
<div class="mobile:grid-col-12 desktop:grid-col-6"> <div class="section--outlined__search {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6{% endif %}">
<section aria-label="Domains search component" class="flex-6 margin-y-2"> <section aria-label="Domains search component" class="margin-top-2">
<form class="usa-search usa-search--small" method="POST" role="search"> <form class="usa-search usa-search--small" method="POST" role="search">
{% csrf_token %} {% csrf_token %}
<button class="usa-button usa-button--unstyled margin-right-3 domains__reset-search display-none" type="button"> <button class="usa-button usa-button--unstyled margin-right-3 domains__reset-search display-none" type="button">
@ -37,10 +35,19 @@
</form> </form>
</section> </section>
</div> </div>
<div class="section--outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
<section aria-label="Domains report component" class="mobile-lg:margin-top-205">
<a href="{% url 'export_data_type_user' %}" class="usa-button usa-button--unstyled" role="button">
<svg class="usa-icon usa-icon--big margin-right-neg-4px" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#file_download"></use>
</svg>Export as CSV
</a>
</section>
</div>
</div> </div>
<!-- Use portfolio_base_permission when merging into 2366 then delete this comment --> <!-- Use portfolio_base_permission when merging into 2366 then delete this comment -->
{% if portfolio %} {% if portfolio %}
<div class="display-flex flex-align-center margin-top-1"> <div class="display-flex flex-align-center">
<span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span> <span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span>
<div class="usa-accordion usa-accordion--select margin-right-2"> <div class="usa-accordion usa-accordion--select margin-right-2">
<div class="usa-accordion__heading"> <div class="usa-accordion__heading">

View file

@ -1,8 +1,64 @@
{% extends 'portfolio_base.html' %} {% extends 'portfolio_base.html' %}
{% load static field_helpers%}
{% block title %}Organization mailing address | {{ portfolio.name }} | {% endblock %}
{% load static %} {% load static %}
{% block portfolio_content %} {% block portfolio_content %}
<h1>Organization</h1> <div class="grid-row grid-gap">
<div class="tablet:grid-col-3">
<p class="font-body-md margin-top-0 margin-bottom-2
text-primary-darker text-semibold"
>
<span class="usa-sr-only"> Portfolio name:</span> {{ portfolio }}
</p>
{% include 'portfolio_organization_sidebar.html' %}
</div>
<div class="tablet:grid-col-9">
<h1>Organization</h1>
<p>The name of your federal agency will be publicly listed as the domain registrant.</p>
<p>
The federal agency for your organization cant be updated here.
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
</p>
{% include "includes/form_errors.html" with form=form %}
{% include "includes/required_fields.html" %}
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
{% csrf_token %}
<p>
<strong class="text-primary display-block margin-bottom-1">Federal agency</strong>
{{ portfolio }}
</p>
{% input_with_errors form.address_line1 %}
{% input_with_errors form.address_line2 %}
{% input_with_errors form.city %}
{% input_with_errors form.state_territory %}
{% with add_class="usa-input--small" %}
{% input_with_errors form.zipcode %}
{% endwith %}
<button
type="submit"
class="usa-button"
>
Save
</button>
</form>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,23 @@
{% load static url_helpers %}
<div class="margin-bottom-4 tablet:margin-bottom-0">
<nav aria-label="Domain sections">
<ul class="usa-sidenav">
<li class="usa-sidenav__item">
{% url 'portfolio-organization' portfolio_id=portfolio.id as url %}
<a href="{{ url }}"
{% if request.path == url %}class="usa-current"{% endif %}
>
Organization
</a>
</li>
<li class="usa-sidenav__item">
<a href="#"
>
Senior official
</a>
</li>
</ul>
</nav>
</div>

View file

@ -41,6 +41,8 @@ from registrar.models.user_domain_role import UserDomainRole
from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
from api.tests.common import less_console_noise_decorator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -525,230 +527,230 @@ class AuditedAdminMockData:
class MockDb(TestCase): class MockDb(TestCase):
"""Hardcoded mocks make test case assertions straightforward."""
def setUp(self): @classmethod
super().setUp() @less_console_noise_decorator
def sharedSetUp(cls):
username = "test_user" username = "test_user"
first_name = "First" first_name = "First"
last_name = "Last" last_name = "Last"
email = "info@example.com" email = "info@example.com"
self.user = get_user_model().objects.create( cls.user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email username=username, first_name=first_name, last_name=last_name, email=email
) )
current_date = get_time_aware_date(datetime(2024, 4, 2)) current_date = get_time_aware_date(datetime(2024, 4, 2))
# Create start and end dates using timedelta # Create start and end dates using timedelta
self.end_date = current_date + timedelta(days=2) cls.end_date = current_date + timedelta(days=2)
self.start_date = current_date - timedelta(days=2) cls.start_date = current_date - timedelta(days=2)
self.federal_agency_1, _ = FederalAgency.objects.get_or_create(agency="World War I Centennial Commission") cls.federal_agency_1, _ = FederalAgency.objects.get_or_create(agency="World War I Centennial Commission")
self.federal_agency_2, _ = FederalAgency.objects.get_or_create(agency="Armed Forces Retirement Home") cls.federal_agency_2, _ = FederalAgency.objects.get_or_create(agency="Armed Forces Retirement Home")
self.domain_1, _ = Domain.objects.get_or_create( cls.domain_1, _ = Domain.objects.get_or_create(
name="cdomain1.gov", state=Domain.State.READY, first_ready=get_time_aware_date(datetime(2024, 4, 2)) name="cdomain1.gov", state=Domain.State.READY, first_ready=get_time_aware_date(datetime(2024, 4, 2))
) )
self.domain_2, _ = Domain.objects.get_or_create(name="adomain2.gov", state=Domain.State.DNS_NEEDED) cls.domain_2, _ = Domain.objects.get_or_create(name="adomain2.gov", state=Domain.State.DNS_NEEDED)
self.domain_3, _ = Domain.objects.get_or_create(name="ddomain3.gov", state=Domain.State.ON_HOLD) cls.domain_3, _ = Domain.objects.get_or_create(name="ddomain3.gov", state=Domain.State.ON_HOLD)
self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN) cls.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN)
self.domain_5, _ = Domain.objects.get_or_create( cls.domain_5, _ = Domain.objects.get_or_create(
name="bdomain5.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(2023, 11, 1)) name="bdomain5.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(2023, 11, 1))
) )
self.domain_6, _ = Domain.objects.get_or_create( cls.domain_6, _ = Domain.objects.get_or_create(
name="bdomain6.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(1980, 10, 16)) name="bdomain6.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(1980, 10, 16))
) )
self.domain_7, _ = Domain.objects.get_or_create( cls.domain_7, _ = Domain.objects.get_or_create(
name="xdomain7.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(2024, 4, 2)) name="xdomain7.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(2024, 4, 2))
) )
self.domain_8, _ = Domain.objects.get_or_create( cls.domain_8, _ = Domain.objects.get_or_create(
name="sdomain8.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(2024, 4, 2)) name="sdomain8.gov", state=Domain.State.DELETED, deleted=get_time_aware_date(datetime(2024, 4, 2))
) )
# We use timezone.make_aware to sync to server time a datetime object with the current date (using date.today()) # We use timezone.make_aware to sync to server time a datetime object with the current date (using date.today())
# and a specific time (using datetime.min.time()). # and a specific time (using datetime.min.time()).
# Deleted yesterday # Deleted yesterday
self.domain_9, _ = Domain.objects.get_or_create( cls.domain_9, _ = Domain.objects.get_or_create(
name="zdomain9.gov", name="zdomain9.gov",
state=Domain.State.DELETED, state=Domain.State.DELETED,
deleted=get_time_aware_date(datetime(2024, 4, 1)), deleted=get_time_aware_date(datetime(2024, 4, 1)),
) )
# ready tomorrow # ready tomorrow
self.domain_10, _ = Domain.objects.get_or_create( cls.domain_10, _ = Domain.objects.get_or_create(
name="adomain10.gov", name="adomain10.gov",
state=Domain.State.READY, state=Domain.State.READY,
first_ready=get_time_aware_date(datetime(2024, 4, 3)), first_ready=get_time_aware_date(datetime(2024, 4, 3)),
) )
self.domain_11, _ = Domain.objects.get_or_create( cls.domain_11, _ = Domain.objects.get_or_create(
name="cdomain11.gov", state=Domain.State.READY, first_ready=get_time_aware_date(datetime(2024, 4, 2)) name="cdomain11.gov", state=Domain.State.READY, first_ready=get_time_aware_date(datetime(2024, 4, 2))
) )
self.domain_12, _ = Domain.objects.get_or_create( cls.domain_12, _ = Domain.objects.get_or_create(
name="zdomain12.gov", state=Domain.State.READY, first_ready=get_time_aware_date(datetime(2024, 4, 2)) name="zdomain12.gov", state=Domain.State.READY, first_ready=get_time_aware_date(datetime(2024, 4, 2))
) )
self.domain_information_1, _ = DomainInformation.objects.get_or_create( cls.domain_information_1, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_1, domain=cls.domain_1,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_1, federal_agency=cls.federal_agency_1,
federal_type="executive", federal_type="executive",
is_election_board=False, is_election_board=False,
) )
self.domain_information_2, _ = DomainInformation.objects.get_or_create( cls.domain_information_2, _ = DomainInformation.objects.get_or_create(
creator=self.user, domain=self.domain_2, generic_org_type="interstate", is_election_board=True creator=cls.user, domain=cls.domain_2, generic_org_type="interstate", is_election_board=True
) )
self.domain_information_3, _ = DomainInformation.objects.get_or_create( cls.domain_information_3, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_3, domain=cls.domain_3,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_4, _ = DomainInformation.objects.get_or_create( cls.domain_information_4, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_4, domain=cls.domain_4,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_5, _ = DomainInformation.objects.get_or_create( cls.domain_information_5, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_5, domain=cls.domain_5,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_6, _ = DomainInformation.objects.get_or_create( cls.domain_information_6, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_6, domain=cls.domain_6,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_7, _ = DomainInformation.objects.get_or_create( cls.domain_information_7, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_7, domain=cls.domain_7,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_8, _ = DomainInformation.objects.get_or_create( cls.domain_information_8, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_8, domain=cls.domain_8,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_9, _ = DomainInformation.objects.get_or_create( cls.domain_information_9, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_9, domain=cls.domain_9,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_10, _ = DomainInformation.objects.get_or_create( cls.domain_information_10, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_10, domain=cls.domain_10,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_2, federal_agency=cls.federal_agency_2,
is_election_board=False, is_election_board=False,
) )
self.domain_information_11, _ = DomainInformation.objects.get_or_create( cls.domain_information_11, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_11, domain=cls.domain_11,
generic_org_type="federal", generic_org_type="federal",
federal_agency=self.federal_agency_1, federal_agency=cls.federal_agency_1,
federal_type="executive", federal_type="executive",
is_election_board=False, is_election_board=False,
) )
self.domain_information_12, _ = DomainInformation.objects.get_or_create( cls.domain_information_12, _ = DomainInformation.objects.get_or_create(
creator=self.user, creator=cls.user,
domain=self.domain_12, domain=cls.domain_12,
generic_org_type="interstate", generic_org_type="interstate",
is_election_board=False, is_election_board=False,
) )
self.meoward_user = get_user_model().objects.create( cls.meoward_user = get_user_model().objects.create(
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com" username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
) )
lebowski_user = get_user_model().objects.create( cls.lebowski_user = get_user_model().objects.create(
username="big_lebowski", first_name="big", last_name="lebowski", email="big_lebowski@dude.co" username="big_lebowski", first_name="big", last_name="lebowski", email="big_lebowski@dude.co"
) )
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=self.meoward_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER user=cls.meoward_user, domain=cls.domain_1, role=UserDomainRole.Roles.MANAGER
) )
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER user=cls.user, domain=cls.domain_1, role=UserDomainRole.Roles.MANAGER
) )
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=lebowski_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER user=cls.lebowski_user, domain=cls.domain_1, role=UserDomainRole.Roles.MANAGER
) )
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=self.meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER user=cls.meoward_user, domain=cls.domain_2, role=UserDomainRole.Roles.MANAGER
) )
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=self.meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER user=cls.meoward_user, domain=cls.domain_11, role=UserDomainRole.Roles.MANAGER
) )
_, created = UserDomainRole.objects.get_or_create( _, created = UserDomainRole.objects.get_or_create(
user=self.meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER user=cls.meoward_user, domain=cls.domain_12, role=UserDomainRole.Roles.MANAGER
) )
_, created = DomainInvitation.objects.get_or_create( _, created = DomainInvitation.objects.get_or_create(
email=self.meoward_user.email, email=cls.meoward_user.email,
domain=self.domain_1, domain=cls.domain_1,
status=DomainInvitation.DomainInvitationStatus.RETRIEVED, status=DomainInvitation.DomainInvitationStatus.RETRIEVED,
) )
_, created = DomainInvitation.objects.get_or_create( _, created = DomainInvitation.objects.get_or_create(
email="woofwardthethird@rocks.com", email="woofwardthethird@rocks.com",
domain=self.domain_1, domain=cls.domain_1,
status=DomainInvitation.DomainInvitationStatus.INVITED, status=DomainInvitation.DomainInvitationStatus.INVITED,
) )
_, created = DomainInvitation.objects.get_or_create( _, created = DomainInvitation.objects.get_or_create(
email="squeaker@rocks.com", domain=self.domain_2, status=DomainInvitation.DomainInvitationStatus.INVITED email="squeaker@rocks.com", domain=cls.domain_2, status=DomainInvitation.DomainInvitationStatus.INVITED
) )
_, created = DomainInvitation.objects.get_or_create( _, created = DomainInvitation.objects.get_or_create(
email="squeaker@rocks.com", domain=self.domain_10, status=DomainInvitation.DomainInvitationStatus.INVITED email="squeaker@rocks.com", domain=cls.domain_10, status=DomainInvitation.DomainInvitationStatus.INVITED
) )
with less_console_noise(): with less_console_noise():
self.domain_request_1 = completed_domain_request( cls.domain_request_1 = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
name="city1.gov", name="city1.gov",
) )
self.domain_request_2 = completed_domain_request( cls.domain_request_2 = completed_domain_request(
status=DomainRequest.DomainRequestStatus.IN_REVIEW, status=DomainRequest.DomainRequestStatus.IN_REVIEW,
name="city2.gov", name="city2.gov",
) )
self.domain_request_3 = completed_domain_request( cls.domain_request_3 = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
name="city3.gov", name="city3.gov",
) )
self.domain_request_4 = completed_domain_request( cls.domain_request_4 = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
name="city4.gov", name="city4.gov",
is_election_board=True, is_election_board=True,
generic_org_type="city", generic_org_type="city",
) )
self.domain_request_5 = completed_domain_request( cls.domain_request_5 = completed_domain_request(
status=DomainRequest.DomainRequestStatus.APPROVED, status=DomainRequest.DomainRequestStatus.APPROVED,
name="city5.gov", name="city5.gov",
) )
self.domain_request_6 = completed_domain_request( cls.domain_request_6 = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
name="city6.gov", name="city6.gov",
) )
self.domain_request_3.submit() cls.domain_request_3.submit()
self.domain_request_4.submit() cls.domain_request_4.submit()
self.domain_request_6.submit() cls.domain_request_6.submit()
other, _ = Contact.objects.get_or_create( other, _ = Contact.objects.get_or_create(
first_name="Testy1232", first_name="Testy1232",
@ -769,29 +771,56 @@ class MockDb(TestCase):
website_3, _ = Website.objects.get_or_create(website="https://www.example.com") website_3, _ = Website.objects.get_or_create(website="https://www.example.com")
website_4, _ = Website.objects.get_or_create(website="https://www.example2.com") website_4, _ = Website.objects.get_or_create(website="https://www.example2.com")
self.domain_request_3.other_contacts.add(other, other_2) cls.domain_request_3.other_contacts.add(other, other_2)
self.domain_request_3.alternative_domains.add(website, website_2) cls.domain_request_3.alternative_domains.add(website, website_2)
self.domain_request_3.current_websites.add(website_3, website_4) cls.domain_request_3.current_websites.add(website_3, website_4)
self.domain_request_3.cisa_representative_email = "test@igorville.com" cls.domain_request_3.cisa_representative_email = "test@igorville.com"
self.domain_request_3.submission_date = get_time_aware_date(datetime(2024, 4, 2)) cls.domain_request_3.submission_date = get_time_aware_date(datetime(2024, 4, 2))
self.domain_request_3.save() cls.domain_request_3.save()
self.domain_request_4.submission_date = get_time_aware_date(datetime(2024, 4, 2)) cls.domain_request_4.submission_date = get_time_aware_date(datetime(2024, 4, 2))
self.domain_request_4.save() cls.domain_request_4.save()
self.domain_request_6.submission_date = get_time_aware_date(datetime(2024, 4, 2)) cls.domain_request_6.submission_date = get_time_aware_date(datetime(2024, 4, 2))
self.domain_request_6.save() cls.domain_request_6.save()
def tearDown(self): @classmethod
super().tearDown() def sharedTearDown(cls):
PublicContact.objects.all().delete() PublicContact.objects.all().delete()
Domain.objects.all().delete() Domain.objects.all().delete()
DomainInformation.objects.all().delete() DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete() DomainRequest.objects.all().delete()
User.objects.all().delete()
UserDomainRole.objects.all().delete() UserDomainRole.objects.all().delete()
User.objects.all().delete()
DomainInvitation.objects.all().delete() DomainInvitation.objects.all().delete()
FederalAgency.objects.all().delete() cls.federal_agency_1.delete()
cls.federal_agency_2.delete()
class MockDbForSharedTests(MockDb):
"""Set up and tear down test data that is shared across all tests in a class"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.sharedSetUp()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.sharedTearDown()
class MockDbForIndividualTests(MockDb):
"""Set up and tear down test data for each test in a class"""
def setUp(self):
super().setUp()
self.sharedSetUp()
def tearDown(self):
super().tearDown()
self.sharedTearDown()
def mock_user(): def mock_user():
@ -840,6 +869,19 @@ def create_user():
return user return user
def create_test_user():
username = "test_user"
first_name = "First"
last_name = "Last"
email = "info@example.com"
phone = "8003111234"
title = "test title"
user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email, phone=phone, title=title
)
return user
def create_ready_domain(): def create_ready_domain():
domain, _ = Domain.objects.get_or_create(name="city.gov", state=Domain.State.READY) domain, _ = Domain.objects.get_or_create(name="city.gov", state=Domain.State.READY)
return domain return domain

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,824 @@
from datetime import date
from django.test import TestCase, RequestFactory, Client, override_settings
from django.contrib.admin.sites import AdminSite
from api.tests.common import less_console_noise_decorator
from django_webtest import WebTest # type: ignore
from django.contrib import messages
from django.urls import reverse
from registrar.admin import (
DomainAdmin,
)
from registrar.models import (
Domain,
DomainRequest,
DomainInformation,
User,
Host,
)
from .common import (
MockSESClient,
completed_domain_request,
less_console_noise,
create_superuser,
create_user,
create_ready_domain,
MockEppLib,
GenericTestHelper,
)
from unittest.mock import ANY, call, patch
import boto3_mocking # type: ignore
import logging
logger = logging.getLogger(__name__)
class TestDomainAdminAsStaff(MockEppLib):
"""Test DomainAdmin class as staff user.
Notes:
all tests share staffuser; do not change staffuser model in tests
tests have available staffuser, client, and admin
"""
@classmethod
def setUpClass(self):
super().setUpClass()
self.staffuser = create_user()
self.site = AdminSite()
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
self.factory = RequestFactory()
def setUp(self):
self.client = Client(HTTP_HOST="localhost:8080")
self.client.force_login(self.staffuser)
super().setUp()
def tearDown(self):
super().tearDown()
Host.objects.all().delete()
Domain.objects.all().delete()
DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete()
@classmethod
def tearDownClass(self):
User.objects.all().delete()
super().tearDownClass()
@less_console_noise_decorator
def test_staff_can_see_cisa_region_federal(self):
"""Tests if staff can see CISA Region: N/A"""
# Create a fake domain request
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
_domain_request.approve()
domain = _domain_request.approved_domain
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Test if the page has the right CISA region
expected_html = '<div class="flex-container margin-top-2"><span>CISA region: N/A</span></div>'
# Remove whitespace from expected_html
expected_html = "".join(expected_html.split())
# Remove whitespace from response content
response_content = "".join(response.content.decode().split())
# Check if response contains expected_html
self.assertIn(expected_html, response_content)
@less_console_noise_decorator
def test_staff_can_see_cisa_region_non_federal(self):
"""Tests if staff can see the correct CISA region"""
# Create a fake domain request. State will be NY (2).
_domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.IN_REVIEW, generic_org_type="interstate"
)
_domain_request.approve()
domain = _domain_request.approved_domain
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Test if the page has the right CISA region
expected_html = '<div class="flex-container margin-top-2"><span>CISA region: 2</span></div>'
# Remove whitespace from expected_html
expected_html = "".join(expected_html.split())
# Remove whitespace from response content
response_content = "".join(response.content.decode().split())
# Check if response contains expected_html
self.assertIn(expected_html, response_content)
@less_console_noise_decorator
def test_analyst_can_see_inline_domain_information_in_domain_change_form(self):
"""Tests if an analyst can still see the inline domain information form"""
# Create fake creator
_creator = User.objects.create(
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
)
# Create a fake domain request
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
# Creates a Domain and DomainInformation object
_domain_request.approve()
domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
domain_information.organization_name = "MonkeySeeMonkeyDo"
domain_information.save()
# We use filter here rather than just domain_information.domain just to get the latest data.
domain = Domain.objects.filter(domain_info=domain_information).get()
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Test for data. We only need to test one since its all interconnected.
expected_organization_name = "MonkeySeeMonkeyDo"
self.assertContains(response, expected_organization_name)
# clean up this test's data
domain.delete()
domain_information.delete()
_domain_request.delete()
_creator.delete()
@less_console_noise_decorator
def test_deletion_is_successful(self):
"""
Scenario: Domain deletion is unsuccessful
When the domain is deleted
Then a user-friendly success message is returned for displaying on the web
And `state` is set to `DELETED`
"""
domain = create_ready_domain()
# Put in client hold
domain.place_client_hold()
# Ensure everything is displaying correctly
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Remove from registry")
# The contents of the modal should exist before and after the post.
# Check for the header
self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
# Check for some of its body
self.assertContains(response, "When a domain is removed from the registry:")
# Check for some of the button content
self.assertContains(response, "Yes, remove from registry")
# Test the info dialog
request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Remove from registry", "name": domain.name},
follow=True,
)
request.user = self.client
with patch("django.contrib.messages.add_message") as mock_add_message:
self.admin.do_delete_domain(request, domain)
mock_add_message.assert_called_once_with(
request,
messages.INFO,
"Domain city.gov has been deleted. Thanks!",
extra_tags="",
fail_silently=False,
)
# The modal should still exist
self.assertContains(response, "Are you sure you want to remove this domain from the registry?")
self.assertContains(response, "When a domain is removed from the registry:")
self.assertContains(response, "Yes, remove from registry")
self.assertEqual(domain.state, Domain.State.DELETED)
# clean up data within this test
domain.delete()
@less_console_noise_decorator
def test_deletion_ready_fsm_failure(self):
"""
Scenario: Domain deletion is unsuccessful
When an error is returned from epplibwrapper
Then a user-friendly error message is returned for displaying on the web
And `state` is not set to `DELETED`
"""
domain = create_ready_domain()
# Ensure everything is displaying correctly
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Remove from registry")
# Test the error
request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Remove from registry", "name": domain.name},
follow=True,
)
request.user = self.client
with patch("django.contrib.messages.add_message") as mock_add_message:
self.admin.do_delete_domain(request, domain)
mock_add_message.assert_called_once_with(
request,
messages.ERROR,
"Error deleting this Domain: "
"Can't switch from state 'ready' to 'deleted'"
", must be either 'dns_needed' or 'on_hold'",
extra_tags="",
fail_silently=False,
)
self.assertEqual(domain.state, Domain.State.READY)
# delete data created in this test
domain.delete()
@less_console_noise_decorator
def test_analyst_deletes_domain_idempotent(self):
"""
Scenario: Analyst tries to delete an already deleted domain
Given `state` is already `DELETED`
When `domain.deletedInEpp()` is called
Then `commands.DeleteDomain` is sent to the registry
And Domain returns normally without an error dialog
"""
domain = create_ready_domain()
# Put in client hold
domain.place_client_hold()
# Ensure everything is displaying correctly
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Remove from registry")
# Test the info dialog
request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Remove from registry", "name": domain.name},
follow=True,
)
request.user = self.client
# Delete it once
with patch("django.contrib.messages.add_message") as mock_add_message:
self.admin.do_delete_domain(request, domain)
mock_add_message.assert_called_once_with(
request,
messages.INFO,
"Domain city.gov has been deleted. Thanks!",
extra_tags="",
fail_silently=False,
)
self.assertEqual(domain.state, Domain.State.DELETED)
# Try to delete it again
# Test the info dialog
request = self.factory.post(
"/admin/registrar/domain/{}/change/".format(domain.pk),
{"_delete_domain": "Remove from registry", "name": domain.name},
follow=True,
)
request.user = self.client
with patch("django.contrib.messages.add_message") as mock_add_message:
self.admin.do_delete_domain(request, domain)
mock_add_message.assert_called_once_with(
request,
messages.INFO,
"This domain is already deleted",
extra_tags="",
fail_silently=False,
)
self.assertEqual(domain.state, Domain.State.DELETED)
# delete data created in this test
domain.delete()
class TestDomainAdminWithClient(TestCase):
"""Test DomainAdmin class as super user.
Notes:
all tests share superuser; tests must not update superuser
tests have available superuser, client, and admin
"""
@classmethod
def setUpClass(self):
super().setUpClass()
self.site = AdminSite()
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
self.factory = RequestFactory()
self.superuser = create_superuser()
def setUp(self):
self.client = Client(HTTP_HOST="localhost:8080")
self.client.force_login(self.superuser)
super().setUp()
def tearDown(self):
super().tearDown()
Host.objects.all().delete()
Domain.objects.all().delete()
DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete()
@classmethod
def tearDownClass(self):
User.objects.all().delete()
super().tearDownClass()
@less_console_noise_decorator
def test_has_model_description(self):
"""Tests if this model has a model description on the table view"""
response = self.client.get(
"/admin/registrar/domain/",
follow=True,
)
# Make sure that the page is loaded correctly
self.assertEqual(response.status_code, 200)
# Test for a description snippet
self.assertContains(response, "This table contains all approved domains in the .gov registrar.")
self.assertContains(response, "Show more")
@less_console_noise_decorator
def test_contact_fields_on_domain_change_form_have_detail_table(self):
"""Tests if the contact fields in the inlined Domain information have the detail table
which displays title, email, and phone"""
# Create fake creator
_creator = User.objects.create(
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
email="meoward.jones@igorville.gov",
phone="(555) 123 12345",
title="Treat inspector",
)
# Create a fake domain request
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
domain_request.approve()
_domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
domain = Domain.objects.filter(domain_info=_domain_info).get()
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Check that the fields have the right values.
# == Check for the creator == #
# Check for the right title, email, and phone number in the response.
# We only need to check for the end tag
# (Otherwise this test will fail if we change classes, etc)
self.assertContains(response, "Treat inspector")
self.assertContains(response, "meoward.jones@igorville.gov")
self.assertContains(response, "(555) 123 12345")
# Check for the field itself
self.assertContains(response, "Meoward Jones")
# == Check for the submitter == #
self.assertContains(response, "mayor@igorville.gov")
self.assertContains(response, "Admin Tester")
self.assertContains(response, "(555) 555 5556")
self.assertContains(response, "Testy2 Tester2")
# == Check for the senior_official == #
self.assertContains(response, "testy@town.com")
self.assertContains(response, "Chief Tester")
self.assertContains(response, "(555) 555 5555")
# Includes things like readonly fields
self.assertContains(response, "Testy Tester")
# == Test the other_employees field == #
self.assertContains(response, "testy2@town.com")
self.assertContains(response, "Another Tester")
self.assertContains(response, "(555) 555 5557")
# Test for the copy link
self.assertContains(response, "usa-button__clipboard")
# cleanup from this test
domain.delete()
_domain_info.delete()
domain_request.delete()
_creator.delete()
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
# Create a ready domain with a preset expiration date
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Contains some test tools
test_helper = GenericTestHelper(
factory=self.factory,
user=self.superuser,
admin=self.admin,
url=reverse("admin:registrar_domain_changelist"),
model=Domain,
client=self.client,
)
# These should exist in the response
expected_values = [
("expiration_date", "Date the domain expires in the registry"),
("first_ready_at", 'Date when this domain first moved into "ready" state; date will never change'),
("deleted_at", 'Will appear blank unless the domain is in "deleted" state'),
]
test_helper.assert_response_contains_distinct_values(response, expected_values)
@less_console_noise_decorator
def test_helper_text_state(self):
"""
Tests for the correct state helper text on this page
"""
# Add domain data
ready_domain, _ = Domain.objects.get_or_create(name="fakeready.gov", state=Domain.State.READY)
unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN)
dns_domain, _ = Domain.objects.get_or_create(name="fakedns.gov", state=Domain.State.DNS_NEEDED)
hold_domain, _ = Domain.objects.get_or_create(name="fakehold.gov", state=Domain.State.ON_HOLD)
deleted_domain, _ = Domain.objects.get_or_create(name="fakedeleted.gov", state=Domain.State.DELETED)
# We don't need to check for all text content, just a portion of it
expected_unknown_domain_message = "The creator of the associated domain request has not logged in to"
expected_dns_message = "Before this domain can be used, name server addresses need"
expected_hold_message = "While on hold, this domain"
expected_deleted_message = "This domain was permanently removed from the registry."
expected_messages = [
(ready_domain, "This domain has name servers and is ready for use."),
(unknown_domain, expected_unknown_domain_message),
(dns_domain, expected_dns_message),
(hold_domain, expected_hold_message),
(deleted_domain, expected_deleted_message),
]
for domain, message in expected_messages:
with self.subTest(domain_state=domain.state):
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.id),
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Check that the right help text exists
self.assertContains(response, message)
@less_console_noise_decorator
def test_admin_can_see_inline_domain_information_in_domain_change_form(self):
"""Tests if an admin can still see the inline domain information form"""
# Create fake creator
_creator = User.objects.create(
username="MrMeoward",
first_name="Meoward",
last_name="Jones",
)
# Create a fake domain request
_domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
# Creates a Domain and DomainInformation object
_domain_request.approve()
domain_information = DomainInformation.objects.filter(domain_request=_domain_request).get()
domain_information.organization_name = "MonkeySeeMonkeyDo"
domain_information.save()
# We use filter here rather than just domain_information.domain just to get the latest data.
domain = Domain.objects.filter(domain_info=domain_information).get()
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Test for data. We only need to test one since its all interconnected.
expected_organization_name = "MonkeySeeMonkeyDo"
self.assertContains(response, expected_organization_name)
# cleanup from this test
domain.delete()
domain_information.delete()
_domain_request.delete()
_creator.delete()
@less_console_noise_decorator
def test_custom_delete_confirmation_page_table(self):
"""Tests if we override the delete confirmation page for custom content on the table"""
# Create a ready domain
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
# Get the index. The post expects the index to be encoded as a string
index = f"{domain.id}"
# Contains some test tools
test_helper = GenericTestHelper(
factory=self.factory,
user=self.superuser,
admin=self.admin,
url=reverse("admin:registrar_domain_changelist"),
model=Domain,
client=self.client,
)
# Simulate selecting a single record, then clicking "Delete selected domains"
response = test_helper.get_table_delete_confirmation_page("0", index)
# Check that our content exists
content_slice = "When a domain is deleted:"
self.assertContains(response, content_slice)
@less_console_noise_decorator
def test_short_org_name_in_domains_list(self):
"""
Make sure the short name is displaying in admin on the list page
"""
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client):
domain_request.approve()
response = self.client.get("/admin/registrar/domain/")
# There are 4 template references to Federal (4) plus four references in the table
# for our actual domain_request
self.assertContains(response, "Federal", count=56)
# This may be a bit more robust
self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
# Now let's make sure the long description does not exist
self.assertNotContains(response, "Federal: an agency of the U.S. government")
@override_settings(IS_PRODUCTION=True)
@less_console_noise_decorator
def test_prod_only_shows_export(self):
"""Test that production environment only displays export"""
response = self.client.get("/admin/registrar/domain/")
self.assertContains(response, ">Export<")
self.assertNotContains(response, ">Import<")
class TestDomainAdminWebTest(MockEppLib, WebTest):
"""Test DomainAdmin class as super user, using WebTest.
WebTest allows for easier handling of forms and html responses.
Notes:
all tests share superuser; tests must not update superuser
tests have available superuser, app, and admin
"""
# csrf checks do not work with WebTest.
# We disable them here. TODO for another ticket.
csrf_checks = False
@classmethod
def setUpClass(self):
super().setUpClass()
self.site = AdminSite()
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
self.superuser = create_superuser()
self.factory = RequestFactory()
def setUp(self):
super().setUp()
self.app.set_user(self.superuser.username)
def tearDown(self):
super().tearDown()
Host.objects.all().delete()
Domain.objects.all().delete()
DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete()
@classmethod
def tearDownClass(self):
User.objects.all().delete()
super().tearDownClass()
@less_console_noise_decorator
@patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
def test_extend_expiration_date_button(self, mock_date_today):
"""
Tests if extend_expiration_date modal gives an accurate date
"""
# Create a ready domain with a preset expiration date
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
# load expiration date into cache and registrar with below command
domain.registry_expiration_date
# Make sure the ex date is what we expect it to be
domain_ex_date = Domain.objects.get(id=domain.id).expiration_date
self.assertEqual(domain_ex_date, date(2023, 5, 25))
# Make sure that the page is loading as expected
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Extend expiration date")
# Grab the form to submit
form = response.forms["domain_form"]
with patch("django.contrib.messages.add_message") as mock_add_message:
# Submit the form
response = form.submit("_extend_expiration_date")
# Follow the response
response = response.follow()
# Assert that everything on the page looks correct
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Extend expiration date")
# Ensure the message we recieve is in line with what we expect
expected_message = "Successfully extended the expiration date."
expected_call = call(
# The WGSI request doesn't need to be tested
ANY,
messages.INFO,
expected_message,
extra_tags="",
fail_silently=False,
)
mock_add_message.assert_has_calls([expected_call], 1)
@less_console_noise_decorator
@patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
def test_extend_expiration_date_button_epp(self, mock_date_today):
"""
Tests if extend_expiration_date button sends the right epp command
"""
# Create a ready domain with a preset expiration date
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
# Make sure that the page is loading as expected
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Extend expiration date")
# Grab the form to submit
form = response.forms["domain_form"]
with patch("django.contrib.messages.add_message") as mock_add_message:
with patch("registrar.models.Domain.renew_domain") as renew_mock:
# Submit the form
response = form.submit("_extend_expiration_date")
# Follow the response
response = response.follow()
# Assert that it is calling the function with the default extension length.
# We only need to test the value that EPP sends, as we can assume the other
# test cases cover the "renew" function.
renew_mock.assert_has_calls([call()], any_order=False)
# We should not make duplicate calls
self.assertEqual(renew_mock.call_count, 1)
# Assert that everything on the page looks correct
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Extend expiration date")
# Ensure the message we recieve is in line with what we expect
expected_message = "Successfully extended the expiration date."
expected_call = call(
# The WGSI request doesn't need to be tested
ANY,
messages.INFO,
expected_message,
extra_tags="",
fail_silently=False,
)
mock_add_message.assert_has_calls([expected_call], 1)
@less_console_noise_decorator
def test_custom_delete_confirmation_page(self):
"""Tests if we override the delete confirmation page for custom content"""
# Create a ready domain with a preset expiration date
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
domain_change_page = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
self.assertContains(domain_change_page, "fake.gov")
# click the "Delete" link
confirmation_page = domain_change_page.click("Delete", index=0)
content_slice = "When a domain is deleted:"
self.assertContains(confirmation_page, content_slice)
@less_console_noise_decorator
def test_on_hold_is_successful_web_test(self):
"""
Scenario: Domain on_hold is successful through webtest
"""
with less_console_noise():
domain = create_ready_domain()
response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk]))
# Check the contents of the modal
# Check for the header
self.assertContains(response, "Are you sure you want to place this domain on hold?")
# Check for some of its body
self.assertContains(response, "When a domain is on hold:")
# Check for some of the button content
self.assertContains(response, "Yes, place hold")
# Grab the form to submit
form = response.forms["domain_form"]
# Submit the form
response = form.submit("_place_client_hold")
# Follow the response
response = response.follow()
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Remove hold")
# The modal should still exist
# Check for the header
self.assertContains(response, "Are you sure you want to place this domain on hold?")
# Check for some of its body
self.assertContains(response, "When a domain is on hold:")
# Check for some of the button content
self.assertContains(response, "Yes, place hold")
# Web test has issues grabbing up to date data from the db, so we can test
# the returned view instead
self.assertContains(response, '<div class="readonly">On hold</div>')

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
from django.test import TestCase, Client from django.test import TestCase, Client
from django.urls import reverse from django.urls import reverse
from registrar.tests.common import create_superuser from registrar.tests.common import create_superuser
from api.tests.common import less_console_noise_decorator
class TestAdminViews(TestCase): class TestAdminViews(TestCase):
@ -8,6 +9,7 @@ class TestAdminViews(TestCase):
self.client = Client(HTTP_HOST="localhost:8080") self.client = Client(HTTP_HOST="localhost:8080")
self.superuser = create_superuser() self.superuser = create_superuser()
@less_console_noise_decorator
def test_export_data_view(self): def test_export_data_view(self):
self.client.force_login(self.superuser) self.client.force_login(self.superuser)

View file

@ -6,8 +6,9 @@ from django.test import TestCase
from waffle.testutils import override_flag from waffle.testutils import override_flag
from registrar.utility import email from registrar.utility import email
from registrar.utility.email import send_templated_email from registrar.utility.email import send_templated_email
from .common import completed_domain_request, less_console_noise from .common import completed_domain_request
from api.tests.common import less_console_noise_decorator
from datetime import datetime from datetime import datetime
import boto3_mocking # type: ignore import boto3_mocking # type: ignore
@ -19,6 +20,7 @@ class TestEmails(TestCase):
@boto3_mocking.patching @boto3_mocking.patching
@override_flag("disable_email_sending", active=True) @override_flag("disable_email_sending", active=True)
@less_console_noise_decorator
def test_disable_email_flag(self): def test_disable_email_flag(self):
"""Test if the 'disable_email_sending' stops emails from being sent""" """Test if the 'disable_email_sending' stops emails from being sent"""
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
@ -36,13 +38,13 @@ class TestEmails(TestCase):
self.assertFalse(self.mock_client.send_email.called) self.assertFalse(self.mock_client.send_email.called)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation(self): def test_submission_confirmation(self):
"""Submission confirmation email works.""" """Submission confirmation email works."""
domain_request = completed_domain_request() domain_request = completed_domain_request()
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
# check that an email was sent # check that an email was sent
self.assertTrue(self.mock_client.send_email.called) self.assertTrue(self.mock_client.send_email.called)
@ -74,12 +76,12 @@ class TestEmails(TestCase):
self.assertIn("Anything else", body) self.assertIn("Anything else", body)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_no_current_website_spacing(self): def test_submission_confirmation_no_current_website_spacing(self):
"""Test line spacing without current_website.""" """Test line spacing without current_website."""
domain_request = completed_domain_request(has_current_website=False) domain_request = completed_domain_request(has_current_website=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Current websites:", body) self.assertNotIn("Current websites:", body)
@ -87,12 +89,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"5555\n\n.gov domain:") self.assertRegex(body, r"5555\n\n.gov domain:")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_current_website_spacing(self): def test_submission_confirmation_current_website_spacing(self):
"""Test line spacing with current_website.""" """Test line spacing with current_website."""
domain_request = completed_domain_request(has_current_website=True) domain_request = completed_domain_request(has_current_website=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("Current websites:", body) self.assertIn("Current websites:", body)
@ -101,12 +103,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"city.com\n\n.gov domain:") self.assertRegex(body, r"city.com\n\n.gov domain:")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_other_contacts_spacing(self): def test_submission_confirmation_other_contacts_spacing(self):
"""Test line spacing with other contacts.""" """Test line spacing with other contacts."""
domain_request = completed_domain_request(has_other_contacts=True) domain_request = completed_domain_request(has_other_contacts=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("Other employees from your organization:", body) self.assertIn("Other employees from your organization:", body)
@ -115,12 +117,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"5557\n\nAnything else") self.assertRegex(body, r"5557\n\nAnything else")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_no_other_contacts_spacing(self): def test_submission_confirmation_no_other_contacts_spacing(self):
"""Test line spacing without other contacts.""" """Test line spacing without other contacts."""
domain_request = completed_domain_request(has_other_contacts=False) domain_request = completed_domain_request(has_other_contacts=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
# spacing should be right between adjacent elements # spacing should be right between adjacent elements
@ -128,12 +130,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"None\n\nAnything else") self.assertRegex(body, r"None\n\nAnything else")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_alternative_govdomain_spacing(self): def test_submission_confirmation_alternative_govdomain_spacing(self):
"""Test line spacing with alternative .gov domain.""" """Test line spacing with alternative .gov domain."""
domain_request = completed_domain_request(has_alternative_gov_domain=True) domain_request = completed_domain_request(has_alternative_gov_domain=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("city1.gov", body) self.assertIn("city1.gov", body)
@ -141,12 +143,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"city.gov\n\nAlternative domains:\ncity1.gov\n\nPurpose of your domain:") self.assertRegex(body, r"city.gov\n\nAlternative domains:\ncity1.gov\n\nPurpose of your domain:")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_no_alternative_govdomain_spacing(self): def test_submission_confirmation_no_alternative_govdomain_spacing(self):
"""Test line spacing without alternative .gov domain.""" """Test line spacing without alternative .gov domain."""
domain_request = completed_domain_request(has_alternative_gov_domain=False) domain_request = completed_domain_request(has_alternative_gov_domain=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("city1.gov", body) self.assertNotIn("city1.gov", body)
@ -154,12 +156,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"city.gov\n\nPurpose of your domain:") self.assertRegex(body, r"city.gov\n\nPurpose of your domain:")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_about_your_organization_spacing(self): def test_submission_confirmation_about_your_organization_spacing(self):
"""Test line spacing with about your organization.""" """Test line spacing with about your organization."""
domain_request = completed_domain_request(has_about_your_organization=True) domain_request = completed_domain_request(has_about_your_organization=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertIn("About your organization:", body) self.assertIn("About your organization:", body)
@ -167,12 +169,12 @@ class TestEmails(TestCase):
self.assertRegex(body, r"10002\n\nAbout your organization:") self.assertRegex(body, r"10002\n\nAbout your organization:")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_no_about_your_organization_spacing(self): def test_submission_confirmation_no_about_your_organization_spacing(self):
"""Test line spacing without about your organization.""" """Test line spacing without about your organization."""
domain_request = completed_domain_request(has_about_your_organization=False) domain_request = completed_domain_request(has_about_your_organization=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("About your organization:", body) self.assertNotIn("About your organization:", body)
@ -180,24 +182,24 @@ class TestEmails(TestCase):
self.assertRegex(body, r"10002\n\nSenior official:") self.assertRegex(body, r"10002\n\nSenior official:")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_anything_else_spacing(self): def test_submission_confirmation_anything_else_spacing(self):
"""Test line spacing with anything else.""" """Test line spacing with anything else."""
domain_request = completed_domain_request(has_anything_else=True) domain_request = completed_domain_request(has_anything_else=True)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
# spacing should be right between adjacent elements # spacing should be right between adjacent elements
self.assertRegex(body, r"5557\n\nAnything else?") self.assertRegex(body, r"5557\n\nAnything else?")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_submission_confirmation_no_anything_else_spacing(self): def test_submission_confirmation_no_anything_else_spacing(self):
"""Test line spacing without anything else.""" """Test line spacing without anything else."""
domain_request = completed_domain_request(has_anything_else=False) domain_request = completed_domain_request(has_anything_else=False)
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
with less_console_noise(): domain_request.submit()
domain_request.submit()
_, kwargs = self.mock_client.send_email.call_args _, kwargs = self.mock_client.send_email.call_args
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
self.assertNotIn("Anything else", body) self.assertNotIn("Anything else", body)
@ -205,6 +207,7 @@ class TestEmails(TestCase):
self.assertRegex(body, r"5557\n\n----") self.assertRegex(body, r"5557\n\n----")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_send_email_with_attachment(self): def test_send_email_with_attachment(self):
with boto3_mocking.clients.handler_for("ses", self.mock_client_class): with boto3_mocking.clients.handler_for("ses", self.mock_client_class):
sender_email = "sender@example.com" sender_email = "sender@example.com"

View file

@ -36,6 +36,7 @@ logger = logging.getLogger(__name__)
class TestPopulateVerificationType(MockEppLib): class TestPopulateVerificationType(MockEppLib):
"""Tests for the populate_organization_type script""" """Tests for the populate_organization_type script"""
@less_console_noise_decorator
def setUp(self): def setUp(self):
"""Creates a fake domain object""" """Creates a fake domain object"""
super().setUp() super().setUp()
@ -133,6 +134,7 @@ class TestPopulateVerificationType(MockEppLib):
class TestPopulateOrganizationType(MockEppLib): class TestPopulateOrganizationType(MockEppLib):
"""Tests for the populate_organization_type script""" """Tests for the populate_organization_type script"""
@less_console_noise_decorator
def setUp(self): def setUp(self):
"""Creates a fake domain object""" """Creates a fake domain object"""
super().setUp() super().setUp()
@ -205,6 +207,7 @@ class TestPopulateOrganizationType(MockEppLib):
): ):
call_command("populate_organization_type", "registrar/tests/data/fake_election_domains.csv") call_command("populate_organization_type", "registrar/tests/data/fake_election_domains.csv")
@less_console_noise_decorator
def assert_expected_org_values_on_request_and_info( def assert_expected_org_values_on_request_and_info(
self, self,
domain_request: DomainRequest, domain_request: DomainRequest,
@ -247,6 +250,7 @@ class TestPopulateOrganizationType(MockEppLib):
"""Does nothing for mocking purposes""" """Does nothing for mocking purposes"""
pass pass
@less_console_noise_decorator
def test_request_and_info_city_not_in_csv(self): def test_request_and_info_city_not_in_csv(self):
""" """
Tests what happens to a city domain that is not defined in the CSV. Tests what happens to a city domain that is not defined in the CSV.
@ -282,6 +286,7 @@ class TestPopulateOrganizationType(MockEppLib):
# All values should be the same # All values should be the same
self.assert_expected_org_values_on_request_and_info(city_request, city_info, expected_values) self.assert_expected_org_values_on_request_and_info(city_request, city_info, expected_values)
@less_console_noise_decorator
def test_request_and_info_federal(self): def test_request_and_info_federal(self):
""" """
Tests what happens to a federal domain after the script is run (should be unchanged). Tests what happens to a federal domain after the script is run (should be unchanged).
@ -316,6 +321,7 @@ class TestPopulateOrganizationType(MockEppLib):
# All values should be the same # All values should be the same
self.assert_expected_org_values_on_request_and_info(federal_request, federal_info, expected_values) self.assert_expected_org_values_on_request_and_info(federal_request, federal_info, expected_values)
@less_console_noise_decorator
def test_request_and_info_tribal_add_election_office(self): def test_request_and_info_tribal_add_election_office(self):
""" """
Tests if a tribal domain in the election csv changes organization_type to TRIBAL - ELECTION Tests if a tribal domain in the election csv changes organization_type to TRIBAL - ELECTION
@ -356,6 +362,7 @@ class TestPopulateOrganizationType(MockEppLib):
self.assert_expected_org_values_on_request_and_info(tribal_request, tribal_info, expected_values) self.assert_expected_org_values_on_request_and_info(tribal_request, tribal_info, expected_values)
@less_console_noise_decorator
def test_request_and_info_tribal_doesnt_remove_election_office(self): def test_request_and_info_tribal_doesnt_remove_election_office(self):
""" """
Tests if a tribal domain in the election csv changes organization_type to TRIBAL_ELECTION Tests if a tribal domain in the election csv changes organization_type to TRIBAL_ELECTION
@ -409,6 +416,7 @@ class TestPopulateOrganizationType(MockEppLib):
class TestPopulateFirstReady(TestCase): class TestPopulateFirstReady(TestCase):
"""Tests for the populate_first_ready script""" """Tests for the populate_first_ready script"""
@less_console_noise_decorator
def setUp(self): def setUp(self):
"""Creates a fake domain object""" """Creates a fake domain object"""
super().setUp() super().setUp()
@ -537,6 +545,7 @@ class TestPopulateFirstReady(TestCase):
class TestPatchAgencyInfo(TestCase): class TestPatchAgencyInfo(TestCase):
@less_console_noise_decorator
def setUp(self): def setUp(self):
self.user, _ = User.objects.get_or_create(username="testuser") self.user, _ = User.objects.get_or_create(username="testuser")
self.domain, _ = Domain.objects.get_or_create(name="testdomain.gov") self.domain, _ = Domain.objects.get_or_create(name="testdomain.gov")
@ -560,6 +569,7 @@ class TestPatchAgencyInfo(TestCase):
class TestExtendExpirationDates(MockEppLib): class TestExtendExpirationDates(MockEppLib):
@less_console_noise_decorator
def setUp(self): def setUp(self):
"""Defines the file name of migration_json and the folder its contained in""" """Defines the file name of migration_json and the folder its contained in"""
super().setUp() super().setUp()
@ -882,6 +892,7 @@ class TestExportTables(MockEppLib):
def tearDown(self): def tearDown(self):
self.logger_patcher.stop() self.logger_patcher.stop()
@less_console_noise_decorator
@patch("os.makedirs") @patch("os.makedirs")
@patch("os.path.exists") @patch("os.path.exists")
@patch("os.remove") @patch("os.remove")
@ -1113,6 +1124,7 @@ class TestImportTables(TestCase):
class TestTransferFederalAgencyType(TestCase): class TestTransferFederalAgencyType(TestCase):
"""Tests for the transfer_federal_agency_type script""" """Tests for the transfer_federal_agency_type script"""
@less_console_noise_decorator
def setUp(self): def setUp(self):
"""Creates a fake domain object""" """Creates a fake domain object"""
super().setUp() super().setUp()
@ -1172,7 +1184,9 @@ class TestTransferFederalAgencyType(TestCase):
User.objects.all().delete() User.objects.all().delete()
Contact.objects.all().delete() Contact.objects.all().delete()
Website.objects.all().delete() Website.objects.all().delete()
FederalAgency.objects.all().delete() FederalAgency.objects.filter(
id__in=[self.amtrak.id, self.legislative_branch.id, self.library_of_congress.id, self.gov_admin.id]
).delete()
def run_transfer_federal_agency_type(self): def run_transfer_federal_agency_type(self):
""" """

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,16 @@
import io import io
from django.test import Client, RequestFactory from django.test import Client, RequestFactory
from io import StringIO from io import StringIO
from registrar.models.domain_request import DomainRequest from registrar.models import (
from registrar.models.domain import Domain DomainRequest,
Domain,
UserDomainRole,
)
from registrar.utility.csv_export import ( from registrar.utility.csv_export import (
DomainDataFull, DomainDataFull,
DomainDataType, DomainDataType,
DomainDataFederal, DomainDataFederal,
DomainDataTypeUser,
DomainGrowth, DomainGrowth,
DomainManaged, DomainManaged,
DomainUnmanaged, DomainUnmanaged,
@ -27,14 +31,14 @@ import boto3_mocking
from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore
from django.utils import timezone from django.utils import timezone
from api.tests.common import less_console_noise_decorator from api.tests.common import less_console_noise_decorator
from .common import MockDb, MockEppLib, less_console_noise, get_time_aware_date from .common import MockDbForSharedTests, MockDbForIndividualTests, MockEppLib, less_console_noise, get_time_aware_date
class CsvReportsTest(MockDb): class CsvReportsTest(MockDbForSharedTests):
"""Tests to determine if we are uploading our reports correctly""" """Tests to determine if we are uploading our reports correctly."""
def setUp(self): def setUp(self):
"""Create fake domain data""" """setup fake comain data"""
super().setUp() super().setUp()
self.client = Client(HTTP_HOST="localhost:8080") self.client = Client(HTTP_HOST="localhost:8080")
self.factory = RequestFactory() self.factory = RequestFactory()
@ -198,17 +202,13 @@ class CsvReportsTest(MockDb):
self.assertEqual(expected_file_content, response.content) self.assertEqual(expected_file_content, response.content)
class ExportDataTest(MockDb, MockEppLib): class ExportDataTest(MockDbForIndividualTests, MockEppLib):
def setUp(self): """Test the ExportData class from csv_export."""
super().setUp()
def tearDown(self):
super().tearDown()
@less_console_noise_decorator @less_console_noise_decorator
def test_domain_data_type(self): def test_domain_data_type(self):
"""Shows security contacts, domain managers, so""" """Shows security contacts, domain managers, so"""
self.maxDiff = None
# Add security email information # Add security email information
self.domain_1.name = "defaultsecurity.gov" self.domain_1.name = "defaultsecurity.gov"
self.domain_1.save() self.domain_1.save()
@ -237,7 +237,7 @@ class ExportDataTest(MockDb, MockEppLib):
"cdomain11.gov,Ready,2024-04-02,(blank),Federal - Executive,World War I Centennial Commission,,,,(blank),,," "cdomain11.gov,Ready,2024-04-02,(blank),Federal - Executive,World War I Centennial Commission,,,,(blank),,,"
"meoward@rocks.com,\n" "meoward@rocks.com,\n"
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,World War I Centennial Commission,,," "defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,World War I Centennial Commission,,,"
',,,(blank),"meoward@rocks.com, info@example.com, big_lebowski@dude.co",' ',,,(blank),"big_lebowski@dude.co, info@example.com, meoward@rocks.com",'
"woofwardthethird@rocks.com\n" "woofwardthethird@rocks.com\n"
"adomain10.gov,Ready,2024-04-03,(blank),Federal,Armed Forces Retirement Home,,,,(blank),,,," "adomain10.gov,Ready,2024-04-03,(blank),Federal,Armed Forces Retirement Home,,,,(blank),,,,"
"squeaker@rocks.com\n" "squeaker@rocks.com\n"
@ -260,6 +260,57 @@ class ExportDataTest(MockDb, MockEppLib):
self.maxDiff = None self.maxDiff = None
self.assertEqual(csv_content, expected_content) self.assertEqual(csv_content, expected_content)
@less_console_noise_decorator
def test_domain_data_type_user(self):
"""Shows security contacts, domain managers, so for the current user"""
# Add security email information
self.domain_1.name = "defaultsecurity.gov"
self.domain_1.save()
# Invoke setter
self.domain_1.security_contact
self.domain_2.security_contact
self.domain_3.security_contact
# Add a first ready date on the first domain. Leaving the others blank.
self.domain_1.first_ready = get_default_start_date()
self.domain_1.save()
# Create a user and associate it with some domains
UserDomainRole.objects.create(user=self.user, domain=self.domain_2)
# Create a request object
factory = RequestFactory()
request = factory.get("/")
request.user = self.user
# Create a CSV file in memory
csv_file = StringIO()
# Call the export functions
DomainDataTypeUser.export_data_to_csv(csv_file, request=request)
# 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 only domains associated with the user
expected_content = (
"Domain name,Status,First ready on,Expiration date,Domain type,Agency,Organization name,"
"City,State,SO,SO email,"
"Security contact email,Domain managers,Invited domain managers\n"
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,World War I Centennial Commission,,,, ,,"
'(blank),"big_lebowski@dude.co, info@example.com, meoward@rocks.com",'
"woofwardthethird@rocks.com\n"
"adomain2.gov,Dns needed,(blank),(blank),Interstate,,,,, ,,(blank),"
'"info@example.com, meoward@rocks.com",squeaker@rocks.com\n'
)
# 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.maxDiff = None
self.assertEqual(csv_content, expected_content)
@less_console_noise_decorator @less_console_noise_decorator
def test_domain_data_full(self): def test_domain_data_full(self):
"""Shows security contacts, filtered by state""" """Shows security contacts, filtered by state"""
@ -370,8 +421,8 @@ class ExportDataTest(MockDb, MockEppLib):
# Call the export functions # Call the export functions
DomainGrowth.export_data_to_csv( DomainGrowth.export_data_to_csv(
csv_file, csv_file,
self.start_date.strftime("%Y-%m-%d"), start_date=self.start_date.strftime("%Y-%m-%d"),
self.end_date.strftime("%Y-%m-%d"), end_date=self.end_date.strftime("%Y-%m-%d"),
) )
# Reset the CSV file's position to the beginning # Reset the CSV file's position to the beginning
csv_file.seek(0) csv_file.seek(0)
@ -412,8 +463,8 @@ class ExportDataTest(MockDb, MockEppLib):
# Call the export functions # Call the export functions
DomainManaged.export_data_to_csv( DomainManaged.export_data_to_csv(
csv_file, csv_file,
self.start_date.strftime("%Y-%m-%d"), start_date=self.start_date.strftime("%Y-%m-%d"),
self.end_date.strftime("%Y-%m-%d"), end_date=self.end_date.strftime("%Y-%m-%d"),
) )
# Reset the CSV file's position to the beginning # Reset the CSV file's position to the beginning
csv_file.seek(0) csv_file.seek(0)
@ -433,7 +484,7 @@ class ExportDataTest(MockDb, MockEppLib):
"\n" "\n"
"Domain name,Domain type,Domain managers,Invited domain managers\n" "Domain name,Domain type,Domain managers,Invited domain managers\n"
"cdomain11.gov,Federal - Executive,meoward@rocks.com,\n" "cdomain11.gov,Federal - Executive,meoward@rocks.com,\n"
'cdomain1.gov,Federal - Executive,"meoward@rocks.com, info@example.com, big_lebowski@dude.co",' 'cdomain1.gov,Federal - Executive,"big_lebowski@dude.co, info@example.com, meoward@rocks.com",'
"woofwardthethird@rocks.com\n" "woofwardthethird@rocks.com\n"
"zdomain12.gov,Interstate,meoward@rocks.com,\n" "zdomain12.gov,Interstate,meoward@rocks.com,\n"
) )
@ -441,6 +492,7 @@ class ExportDataTest(MockDb, MockEppLib):
# spaces and leading/trailing whitespace # spaces and leading/trailing whitespace
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
self.maxDiff = None
self.assertEqual(csv_content, expected_content) self.assertEqual(csv_content, expected_content)
@less_console_noise_decorator @less_console_noise_decorator
@ -449,7 +501,7 @@ class ExportDataTest(MockDb, MockEppLib):
# Create a CSV file in memory # Create a CSV file in memory
csv_file = StringIO() csv_file = StringIO()
DomainUnmanaged.export_data_to_csv( DomainUnmanaged.export_data_to_csv(
csv_file, self.start_date.strftime("%Y-%m-%d"), self.end_date.strftime("%Y-%m-%d") csv_file, start_date=self.start_date.strftime("%Y-%m-%d"), end_date=self.end_date.strftime("%Y-%m-%d")
) )
# Reset the CSV file's position to the beginning # Reset the CSV file's position to the beginning
@ -496,8 +548,8 @@ class ExportDataTest(MockDb, MockEppLib):
# Call the export functions # Call the export functions
DomainRequestGrowth.export_data_to_csv( DomainRequestGrowth.export_data_to_csv(
csv_file, csv_file,
self.start_date.strftime("%Y-%m-%d"), start_date=self.start_date.strftime("%Y-%m-%d"),
self.end_date.strftime("%Y-%m-%d"), end_date=self.end_date.strftime("%Y-%m-%d"),
) )
# Reset the CSV file's position to the beginning # Reset the CSV file's position to the beginning
csv_file.seek(0) csv_file.seek(0)
@ -595,7 +647,7 @@ class ExportDataTest(MockDb, MockEppLib):
self.assertEqual(csv_content, expected_content) self.assertEqual(csv_content, expected_content)
class HelperFunctions(MockDb): class HelperFunctions(MockDbForSharedTests):
"""This asserts that 1=1. Its limited usefulness lies in making sure the helper methods stay healthy.""" """This asserts that 1=1. Its limited usefulness lies in making sure the helper methods stay healthy."""
def test_get_default_start_date(self): def test_get_default_start_date(self):

View file

@ -43,7 +43,6 @@ class TestProcessedMigrations(TestCase):
DomainInformation.objects.all().delete() DomainInformation.objects.all().delete()
DomainInvitation.objects.all().delete() DomainInvitation.objects.all().delete()
TransitionDomain.objects.all().delete() TransitionDomain.objects.all().delete()
FederalAgency.objects.all().delete()
# Delete users # Delete users
User.objects.all().delete() User.objects.all().delete()
@ -185,6 +184,7 @@ class TestOrganizationMigration(TestCase):
"""Defines the file name of migration_json and the folder its contained in""" """Defines the file name of migration_json and the folder its contained in"""
self.test_data_file_location = "registrar/tests/data" self.test_data_file_location = "registrar/tests/data"
self.migration_json_filename = "test_migrationFilepaths.json" self.migration_json_filename = "test_migrationFilepaths.json"
self.federal_agency, _ = FederalAgency.objects.get_or_create(agency="Department of Commerce")
def tearDown(self): def tearDown(self):
"""Deletes all DB objects related to migrations""" """Deletes all DB objects related to migrations"""
@ -197,6 +197,7 @@ class TestOrganizationMigration(TestCase):
# Delete users # Delete users
User.objects.all().delete() User.objects.all().delete()
UserDomainRole.objects.all().delete() UserDomainRole.objects.all().delete()
self.federal_agency.delete()
def run_load_domains(self): def run_load_domains(self):
""" """
@ -331,7 +332,6 @@ class TestOrganizationMigration(TestCase):
# Lets test the first one # Lets test the first one
transition = transition_domains.first() transition = transition_domains.first()
federal_agency, _ = FederalAgency.objects.get_or_create(agency="Department of Commerce")
expected_transition_domain = TransitionDomain( expected_transition_domain = TransitionDomain(
username="alexandra.bobbitt5@test.com", username="alexandra.bobbitt5@test.com",
domain_name="fakewebsite2.gov", domain_name="fakewebsite2.gov",
@ -340,7 +340,7 @@ class TestOrganizationMigration(TestCase):
generic_org_type="Federal", generic_org_type="Federal",
organization_name="Fanoodle", organization_name="Fanoodle",
federal_type="Executive", federal_type="Executive",
federal_agency=federal_agency, federal_agency=self.federal_agency,
epp_creation_date=datetime.date(2004, 5, 7), epp_creation_date=datetime.date(2004, 5, 7),
epp_expiration_date=datetime.date(2023, 9, 30), epp_expiration_date=datetime.date(2023, 9, 30),
first_name="Seline", first_name="Seline",
@ -395,7 +395,6 @@ class TestOrganizationMigration(TestCase):
# == Third, test that we've loaded data as we expect == # # == Third, test that we've loaded data as we expect == #
_domain = Domain.objects.filter(name="fakewebsite2.gov").get() _domain = Domain.objects.filter(name="fakewebsite2.gov").get()
domain_information = DomainInformation.objects.filter(domain=_domain).get() domain_information = DomainInformation.objects.filter(domain=_domain).get()
federal_agency, _ = FederalAgency.objects.get_or_create(agency="Department of Commerce")
expected_creator = User.objects.filter(username="System").get() expected_creator = User.objects.filter(username="System").get()
expected_so = Contact.objects.filter( expected_so = Contact.objects.filter(
@ -404,7 +403,7 @@ class TestOrganizationMigration(TestCase):
expected_domain_information = DomainInformation( expected_domain_information = DomainInformation(
creator=expected_creator, creator=expected_creator,
generic_org_type="federal", generic_org_type="federal",
federal_agency=federal_agency, federal_agency=self.federal_agency,
federal_type="executive", federal_type="executive",
organization_name="Fanoodle", organization_name="Fanoodle",
address_line1="93001 Arizona Drive", address_line1="93001 Arizona Drive",
@ -451,7 +450,6 @@ class TestOrganizationMigration(TestCase):
# == Fourth, test that no data is overwritten as we expect == # # == Fourth, test that no data is overwritten as we expect == #
_domain = Domain.objects.filter(name="fakewebsite2.gov").get() _domain = Domain.objects.filter(name="fakewebsite2.gov").get()
domain_information = DomainInformation.objects.filter(domain=_domain).get() domain_information = DomainInformation.objects.filter(domain=_domain).get()
federal_agency, _ = FederalAgency.objects.get_or_create(agency="Department of Commerce")
expected_creator = User.objects.filter(username="System").get() expected_creator = User.objects.filter(username="System").get()
expected_so = Contact.objects.filter( expected_so = Contact.objects.filter(
@ -460,7 +458,7 @@ class TestOrganizationMigration(TestCase):
expected_domain_information = DomainInformation( expected_domain_information = DomainInformation(
creator=expected_creator, creator=expected_creator,
generic_org_type="federal", generic_org_type="federal",
federal_agency=federal_agency, federal_agency=self.federal_agency,
federal_type="executive", federal_type="executive",
organization_name="Fanoodle", organization_name="Fanoodle",
address_line1="93001 Galactic Way", address_line1="93001 Galactic Way",

View file

@ -8,14 +8,14 @@ from api.tests.common import less_console_noise_decorator
from registrar.models.contact import Contact from registrar.models.contact import Contact
from registrar.models.domain import Domain from registrar.models.domain import Domain
from registrar.models.draft_domain import DraftDomain from registrar.models.draft_domain import DraftDomain
from registrar.models.federal_agency import FederalAgency
from registrar.models.portfolio import Portfolio from registrar.models.portfolio import Portfolio
from registrar.models.public_contact import PublicContact from registrar.models.public_contact import PublicContact
from registrar.models.user import User from registrar.models.user import User
from registrar.models.user_domain_role import UserDomainRole from registrar.models.user_domain_role import UserDomainRole
from registrar.views.domain import DomainNameserversView from registrar.views.domain import DomainNameserversView
from registrar.models import SeniorOfficial, Suborganization from registrar.models import SeniorOfficial, Suborganization
from .common import MockEppLib, create_test_user, less_console_noise # type: ignore
from .common import MockEppLib, less_console_noise # type: ignore
from unittest.mock import patch from unittest.mock import patch
from django.urls import reverse from django.urls import reverse
@ -31,18 +31,23 @@ logger = logging.getLogger(__name__)
class TestViews(TestCase): class TestViews(TestCase):
def setUp(self): def setUp(self):
super().setUp()
self.client = Client() self.client = Client()
@less_console_noise_decorator
def test_health_check_endpoint(self): def test_health_check_endpoint(self):
response = self.client.get("/health") response = self.client.get("/health")
self.assertContains(response, "OK", status_code=200) self.assertContains(response, "OK", status_code=200)
@less_console_noise_decorator
def test_home_page(self): def test_home_page(self):
"""Home page should NOT be available without a login.""" """Home page should NOT be available without a login."""
response = self.client.get("/") response = self.client.get("/")
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@less_console_noise_decorator
def test_domain_request_form_not_logged_in(self): def test_domain_request_form_not_logged_in(self):
"""Domain request form not accessible without a logged-in user.""" """Domain request form not accessible without a logged-in user."""
response = self.client.get("/request/") response = self.client.get("/request/")
@ -51,43 +56,23 @@ class TestViews(TestCase):
class TestWithUser(MockEppLib): class TestWithUser(MockEppLib):
"""Class for executing tests with a test user.
Note that tests share the test user within their test class, so the user
cannot be changed within a test."""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = create_test_user()
def setUp(self): def setUp(self):
super().setUp() super().setUp()
username = "test_user" self.client = Client()
first_name = "First"
last_name = "Last"
email = "info@example.com"
phone = "8003111234"
title = "test title"
self.user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, title=title, email=email, phone=phone
)
username_regular_incomplete = "test_regular_user_incomplete" @classmethod
username_other_incomplete = "test_other_user_incomplete" def tearDownClass(cls):
first_name_2 = "Incomplete" super().tearDownClass()
email_2 = "unicorn@igorville.com" User.objects.all().delete()
# in the case below, REGULAR user is 'Verified by Login.gov, ie. IAL2
self.incomplete_regular_user = get_user_model().objects.create(
username=username_regular_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.REGULAR,
)
# in the case below, other user is representative of GRANDFATHERED,
# VERIFIED_BY_STAFF, INVITED, FIXTURE_USER, ie. IAL1
self.incomplete_other_user = get_user_model().objects.create(
username=username_other_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.VERIFIED_BY_STAFF,
)
def tearDown(self):
# delete any domain requests too
super().tearDown()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
# For some reason, if this is done on the test directly, # For some reason, if this is done on the test directly,
# we get a django.db.models.deletion.ProtectedError on "User". # we get a django.db.models.deletion.ProtectedError on "User".
# In either event, it doesn't hurt to have these here given their # In either event, it doesn't hurt to have these here given their
@ -96,38 +81,43 @@ class TestWithUser(MockEppLib):
Portfolio.objects.all().delete() Portfolio.objects.all().delete()
SeniorOfficial.objects.all().delete() SeniorOfficial.objects.all().delete()
User.objects.all().delete()
class TestEnvironmentVariablesEffects(TestCase): class TestEnvironmentVariablesEffects(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = create_test_user()
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
username = "test_user"
first_name = "First"
last_name = "Last"
email = "info@example.com"
self.user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email
)
self.client.force_login(self.user) self.client.force_login(self.user)
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
UserDomainRole.objects.all().delete()
Domain.objects.all().delete() Domain.objects.all().delete()
self.user.delete()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
User.objects.all().delete()
@less_console_noise_decorator
@override_settings(IS_PRODUCTION=True) @override_settings(IS_PRODUCTION=True)
def test_production_environment(self): def test_production_environment(self):
"""No banner on prod.""" """No banner on prod."""
home_page = self.client.get("/") home_page = self.client.get("/")
self.assertNotContains(home_page, "You are on a test site.") self.assertNotContains(home_page, "You are on a test site.")
@less_console_noise_decorator
@override_settings(IS_PRODUCTION=False) @override_settings(IS_PRODUCTION=False)
def test_non_production_environment(self): def test_non_production_environment(self):
"""Banner on non-prod.""" """Banner on non-prod."""
home_page = self.client.get("/") home_page = self.client.get("/")
self.assertContains(home_page, "You are on a test site.") self.assertContains(home_page, "You are on a test site.")
@less_console_noise_decorator
def side_effect_raise_value_error(self): def side_effect_raise_value_error(self):
"""Side effect that raises a 500 error""" """Side effect that raises a 500 error"""
raise ValueError("Some error") raise ValueError("Some error")
@ -139,9 +129,7 @@ class TestEnvironmentVariablesEffects(TestCase):
fake_domain, _ = Domain.objects.get_or_create(name="igorville.gov") fake_domain, _ = Domain.objects.get_or_create(name="igorville.gov")
# Add a role # Add a role
fake_role, _ = UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(user=self.user, domain=fake_domain, role=UserDomainRole.Roles.MANAGER)
user=self.user, domain=fake_domain, role=UserDomainRole.Roles.MANAGER
)
with patch.object(DomainNameserversView, "get_initial", side_effect=self.side_effect_raise_value_error): with patch.object(DomainNameserversView, "get_initial", side_effect=self.side_effect_raise_value_error):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -162,9 +150,7 @@ class TestEnvironmentVariablesEffects(TestCase):
fake_domain, _ = Domain.objects.get_or_create(name="igorville.gov") fake_domain, _ = Domain.objects.get_or_create(name="igorville.gov")
# Add a role # Add a role
fake_role, _ = UserDomainRole.objects.get_or_create( UserDomainRole.objects.get_or_create(user=self.user, domain=fake_domain, role=UserDomainRole.Roles.MANAGER)
user=self.user, domain=fake_domain, role=UserDomainRole.Roles.MANAGER
)
with patch.object(DomainNameserversView, "get_initial", side_effect=self.side_effect_raise_value_error): with patch.object(DomainNameserversView, "get_initial", side_effect=self.side_effect_raise_value_error):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -185,15 +171,13 @@ class HomeTests(TestWithUser):
super().setUp() super().setUp()
self.client.force_login(self.user) self.client.force_login(self.user)
def tearDown(self): @less_console_noise_decorator
super().tearDown()
Contact.objects.all().delete()
def test_empty_domain_table(self): def test_empty_domain_table(self):
response = self.client.get("/") response = self.client.get("/")
self.assertContains(response, "You don't have any registered domains.") self.assertContains(response, "You don't have any registered domains.")
self.assertContains(response, "Why don't I see my domain when I sign in to the registrar?") self.assertContains(response, "Why don't I see my domain when I sign in to the registrar?")
@less_console_noise_decorator
def test_state_help_text(self): def test_state_help_text(self):
"""Tests if each domain state has help text""" """Tests if each domain state has help text"""
@ -235,6 +219,7 @@ class HomeTests(TestWithUser):
user_role.delete() user_role.delete()
test_domain.delete() test_domain.delete()
@less_console_noise_decorator
def test_state_help_text_expired(self): def test_state_help_text_expired(self):
"""Tests if each domain state has help text when expired""" """Tests if each domain state has help text when expired"""
expired_text = "This domain has expired, but it is still online. " expired_text = "This domain has expired, but it is still online. "
@ -242,7 +227,9 @@ class HomeTests(TestWithUser):
test_domain.expiration_date = date(2011, 10, 10) test_domain.expiration_date = date(2011, 10, 10)
test_domain.save() test_domain.save()
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) test_role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER
)
# Grab the json response of the domains list # Grab the json response of the domains list
response = self.client.get("/get-domains-json/") response = self.client.get("/get-domains-json/")
@ -253,6 +240,10 @@ class HomeTests(TestWithUser):
# Check that we have the right text content. # Check that we have the right text content.
self.assertContains(response, expired_text, count=1) self.assertContains(response, expired_text, count=1)
test_role.delete()
test_domain.delete()
@less_console_noise_decorator
def test_state_help_text_no_expiration_date(self): def test_state_help_text_no_expiration_date(self):
"""Tests if each domain state has help text when expiration date is None""" """Tests if each domain state has help text when expiration date is None"""
@ -296,6 +287,10 @@ class HomeTests(TestWithUser):
# Check that we have the right text content. # Check that we have the right text content.
self.assertContains(response, unknown_text, count=1) self.assertContains(response, unknown_text, count=1)
UserDomainRole.objects.all().delete()
Domain.objects.all().delete()
@less_console_noise_decorator
def test_home_deletes_withdrawn_domain_request(self): def test_home_deletes_withdrawn_domain_request(self):
"""Tests if the user can delete a DomainRequest in the 'withdrawn' status""" """Tests if the user can delete a DomainRequest in the 'withdrawn' status"""
@ -312,6 +307,7 @@ class HomeTests(TestWithUser):
# clean up # clean up
domain_request.delete() domain_request.delete()
@less_console_noise_decorator
def test_home_deletes_started_domain_request(self): def test_home_deletes_started_domain_request(self):
"""Tests if the user can delete a DomainRequest in the 'started' status""" """Tests if the user can delete a DomainRequest in the 'started' status"""
@ -361,6 +357,7 @@ class HomeTests(TestWithUser):
# clean up # clean up
domain_request.delete() domain_request.delete()
@less_console_noise_decorator
def test_home_deletes_domain_request_and_orphans(self): def test_home_deletes_domain_request_and_orphans(self):
"""Tests if delete for DomainRequest deletes orphaned Contact objects""" """Tests if delete for DomainRequest deletes orphaned Contact objects"""
@ -430,6 +427,10 @@ class HomeTests(TestWithUser):
self.assertEqual(edge_case, contact_2) self.assertEqual(edge_case, contact_2)
DomainRequest.objects.all().delete()
Contact.objects.all().delete()
@less_console_noise_decorator
def test_home_deletes_domain_request_and_shared_orphans(self): def test_home_deletes_domain_request_and_shared_orphans(self):
"""Test the edge case for an object that will become orphaned after a delete """Test the edge case for an object that will become orphaned after a delete
(but is not an orphan at the time of deletion)""" (but is not an orphan at the time of deletion)"""
@ -490,6 +491,10 @@ class HomeTests(TestWithUser):
orphan = Contact.objects.filter(id=contact_shared.id) orphan = Contact.objects.filter(id=contact_shared.id)
self.assertFalse(orphan.exists()) self.assertFalse(orphan.exists())
DomainRequest.objects.all().delete()
Contact.objects.all().delete()
@less_console_noise_decorator
def test_domain_request_form_view(self): def test_domain_request_form_view(self):
response = self.client.get("/request/", follow=True) response = self.client.get("/request/", follow=True)
self.assertContains( self.assertContains(
@ -497,16 +502,24 @@ class HomeTests(TestWithUser):
"Youre about to start your .gov domain request.", "Youre about to start your .gov domain request.",
) )
@less_console_noise_decorator
def test_domain_request_form_with_ineligible_user(self): def test_domain_request_form_with_ineligible_user(self):
"""Domain request form not accessible for an ineligible user. """Domain request form not accessible for an ineligible user.
This test should be solid enough since all domain request wizard This test should be solid enough since all domain request wizard
views share the same permissions class""" views share the same permissions class"""
self.user.status = User.RESTRICTED username = "restricted_user"
self.user.save() first_name = "First"
last_name = "Last"
with less_console_noise(): email = "restricted@example.com"
response = self.client.get("/request/", follow=True) phone = "8003111234"
self.assertEqual(response.status_code, 403) status = User.RESTRICTED
restricted_user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email, phone=phone, status=status
)
self.client.force_login(restricted_user)
response = self.client.get("/request/", follow=True)
self.assertEqual(response.status_code, 403)
restricted_user.delete()
class FinishUserProfileTests(TestWithUser, WebTest): class FinishUserProfileTests(TestWithUser, WebTest):
@ -518,6 +531,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.initial_user_title = self.user.title
self.user.title = None self.user.title = None
self.user.save() self.user.save()
self.client.force_login(self.user) self.client.force_login(self.user)
@ -528,6 +542,10 @@ class FinishUserProfileTests(TestWithUser, WebTest):
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
self.user.title = self.initial_user_title
self.user.save()
PublicContact.objects.filter(domain=self.domain).delete() PublicContact.objects.filter(domain=self.domain).delete()
self.role.delete() self.role.delete()
self.domain.delete() self.domain.delete()
@ -552,48 +570,70 @@ class FinishUserProfileTests(TestWithUser, WebTest):
def test_full_name_initial_value(self): def test_full_name_initial_value(self):
"""Test that full_name initial value is empty when first_name or last_name is empty. """Test that full_name initial value is empty when first_name or last_name is empty.
This will later be displayed as "unknown" using javascript.""" This will later be displayed as "unknown" using javascript."""
self.app.set_user(self.incomplete_regular_user.username) username_regular_incomplete = "test_regular_user_incomplete"
first_name_2 = "Incomplete"
email_2 = "unicorn@igorville.com"
incomplete_regular_user = get_user_model().objects.create(
username=username_regular_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.REGULAR,
)
self.app.set_user(incomplete_regular_user.username)
# Test when first_name is empty # Test when first_name is empty
self.incomplete_regular_user.first_name = "" incomplete_regular_user.first_name = ""
self.incomplete_regular_user.last_name = "Doe" incomplete_regular_user.last_name = "Doe"
self.incomplete_regular_user.save() incomplete_regular_user.save()
finish_setup_page = self.app.get(reverse("home")).follow() finish_setup_page = self.app.get(reverse("home")).follow()
form = finish_setup_page.form form = finish_setup_page.form
self.assertEqual(form["full_name"].value, "") self.assertEqual(form["full_name"].value, "")
# Test when last_name is empty # Test when last_name is empty
self.incomplete_regular_user.first_name = "John" incomplete_regular_user.first_name = "John"
self.incomplete_regular_user.last_name = "" incomplete_regular_user.last_name = ""
self.incomplete_regular_user.save() incomplete_regular_user.save()
finish_setup_page = self.app.get(reverse("home")).follow() finish_setup_page = self.app.get(reverse("home")).follow()
form = finish_setup_page.form form = finish_setup_page.form
self.assertEqual(form["full_name"].value, "") self.assertEqual(form["full_name"].value, "")
# Test when both first_name and last_name are empty # Test when both first_name and last_name are empty
self.incomplete_regular_user.first_name = "" incomplete_regular_user.first_name = ""
self.incomplete_regular_user.last_name = "" incomplete_regular_user.last_name = ""
self.incomplete_regular_user.save() incomplete_regular_user.save()
finish_setup_page = self.app.get(reverse("home")).follow() finish_setup_page = self.app.get(reverse("home")).follow()
form = finish_setup_page.form form = finish_setup_page.form
self.assertEqual(form["full_name"].value, "") self.assertEqual(form["full_name"].value, "")
# Test when both first_name and last_name are present # Test when both first_name and last_name are present
self.incomplete_regular_user.first_name = "John" incomplete_regular_user.first_name = "John"
self.incomplete_regular_user.last_name = "Doe" incomplete_regular_user.last_name = "Doe"
self.incomplete_regular_user.save() incomplete_regular_user.save()
finish_setup_page = self.app.get(reverse("home")).follow() finish_setup_page = self.app.get(reverse("home")).follow()
form = finish_setup_page.form form = finish_setup_page.form
self.assertEqual(form["full_name"].value, "John Doe") self.assertEqual(form["full_name"].value, "John Doe")
incomplete_regular_user.delete()
@less_console_noise_decorator @less_console_noise_decorator
def test_new_user_with_profile_feature_on(self): def test_new_user_with_profile_feature_on(self):
"""Tests that a new user is redirected to the profile setup page when profile_feature is on""" """Tests that a new user is redirected to the profile setup page when profile_feature is on"""
self.app.set_user(self.incomplete_regular_user.username) username_regular_incomplete = "test_regular_user_incomplete"
first_name_2 = "Incomplete"
email_2 = "unicorn@igorville.com"
# in the case below, REGULAR user is 'Verified by Login.gov, ie. IAL2
incomplete_regular_user = get_user_model().objects.create(
username=username_regular_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.REGULAR,
)
self.app.set_user(incomplete_regular_user.username)
with override_flag("profile_feature", active=True): with override_flag("profile_feature", active=True):
# This will redirect the user to the setup page. # This will redirect the user to the setup page.
# Follow implicity checks if our redirect is working. # Follow implicity checks if our redirect is working.
@ -627,14 +667,22 @@ class FinishUserProfileTests(TestWithUser, WebTest):
# This is the same as clicking the back button. # This is the same as clicking the back button.
completed_setup_page = self.app.get(reverse("home")) completed_setup_page = self.app.get(reverse("home"))
self.assertContains(completed_setup_page, "Manage your domain") self.assertContains(completed_setup_page, "Manage your domain")
incomplete_regular_user.delete()
@less_console_noise_decorator @less_console_noise_decorator
def test_new_user_with_empty_name_can_add_name(self): def test_new_user_with_empty_name_can_add_name(self):
"""Tests that a new user without a name can still enter this information accordingly""" """Tests that a new user without a name can still enter this information accordingly"""
self.incomplete_regular_user.first_name = "" username_regular_incomplete = "test_regular_user_incomplete"
self.incomplete_regular_user.last_name = "" email = "unicorn@igorville.com"
self.incomplete_regular_user.save() # in the case below, REGULAR user is 'Verified by Login.gov, ie. IAL2
self.app.set_user(self.incomplete_regular_user.username) incomplete_regular_user = get_user_model().objects.create(
username=username_regular_incomplete,
first_name="",
last_name="",
email=email,
verification_type=User.VerificationTypeChoices.REGULAR,
)
self.app.set_user(incomplete_regular_user.username)
with override_flag("profile_feature", active=True): with override_flag("profile_feature", active=True):
# This will redirect the user to the setup page. # This will redirect the user to the setup page.
# Follow implicity checks if our redirect is working. # Follow implicity checks if our redirect is working.
@ -670,12 +718,22 @@ class FinishUserProfileTests(TestWithUser, WebTest):
# This is the same as clicking the back button. # This is the same as clicking the back button.
completed_setup_page = self.app.get(reverse("home")) completed_setup_page = self.app.get(reverse("home"))
self.assertContains(completed_setup_page, "Manage your domain") self.assertContains(completed_setup_page, "Manage your domain")
incomplete_regular_user.delete()
@less_console_noise_decorator @less_console_noise_decorator
def test_new_user_goes_to_domain_request_with_profile_feature_on(self): def test_new_user_goes_to_domain_request_with_profile_feature_on(self):
"""Tests that a new user is redirected to the domain request page when profile_feature is on""" """Tests that a new user is redirected to the domain request page when profile_feature is on"""
username_regular_incomplete = "test_regular_user_incomplete"
self.app.set_user(self.incomplete_regular_user.username) first_name_2 = "Incomplete"
email_2 = "unicorn@igorville.com"
# in the case below, REGULAR user is 'Verified by Login.gov, ie. IAL2
incomplete_regular_user = get_user_model().objects.create(
username=username_regular_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.REGULAR,
)
self.app.set_user(incomplete_regular_user.username)
with override_flag("profile_feature", active=True): with override_flag("profile_feature", active=True):
# This will redirect the user to the setup page # This will redirect the user to the setup page
finish_setup_page = self.app.get(reverse("domain-request:")).follow() finish_setup_page = self.app.get(reverse("domain-request:")).follow()
@ -718,6 +776,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
self.assertNotContains(completed_setup_page, "What contact information should we use to reach you?") self.assertNotContains(completed_setup_page, "What contact information should we use to reach you?")
self.assertContains(completed_setup_page, "Youre about to start your .gov domain request") self.assertContains(completed_setup_page, "Youre about to start your .gov domain request")
incomplete_regular_user.delete()
@less_console_noise_decorator @less_console_noise_decorator
def test_new_user_with_profile_feature_off(self): def test_new_user_with_profile_feature_off(self):
@ -748,6 +807,7 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.initial_user_title = self.user.title
self.user.title = None self.user.title = None
self.user.save() self.user.save()
self.client.force_login(self.user) self.client.force_login(self.user)
@ -758,6 +818,8 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
self.user.title = self.initial_user_title
self.user.save()
PublicContact.objects.filter(domain=self.domain).delete() PublicContact.objects.filter(domain=self.domain).delete()
self.role.delete() self.role.delete()
Domain.objects.all().delete() Domain.objects.all().delete()
@ -777,7 +839,18 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
def test_new_user_with_profile_feature_on(self): def test_new_user_with_profile_feature_on(self):
"""Tests that a new user is redirected to the profile setup page when profile_feature is on, """Tests that a new user is redirected to the profile setup page when profile_feature is on,
and testing that the confirmation modal is present""" and testing that the confirmation modal is present"""
self.app.set_user(self.incomplete_other_user.username) username_other_incomplete = "test_other_user_incomplete"
first_name_2 = "Incomplete"
email_2 = "unicorn@igorville.com"
# in the case below, other user is representative of GRANDFATHERED,
# VERIFIED_BY_STAFF, INVITED, FIXTURE_USER, ie. IAL1
incomplete_other_user = get_user_model().objects.create(
username=username_other_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.VERIFIED_BY_STAFF,
)
self.app.set_user(incomplete_other_user.username)
with override_flag("profile_feature", active=True): with override_flag("profile_feature", active=True):
# This will redirect the user to the user profile page. # This will redirect the user to the user profile page.
# Follow implicity checks if our redirect is working. # Follow implicity checks if our redirect is working.
@ -856,9 +929,10 @@ class UserProfileTests(TestWithUser, WebTest):
PublicContact.objects.filter(domain=self.domain).delete() PublicContact.objects.filter(domain=self.domain).delete()
self.role.delete() self.role.delete()
self.domain.delete() self.domain.delete()
Contact.objects.all().delete()
DraftDomain.objects.all().delete()
DomainRequest.objects.all().delete() DomainRequest.objects.all().delete()
DraftDomain.objects.all().delete()
Contact.objects.all().delete()
DomainInformation.objects.all().delete()
@less_console_noise_decorator @less_console_noise_decorator
def error_500_main_nav_with_profile_feature_turned_on(self): def error_500_main_nav_with_profile_feature_turned_on(self):
@ -1032,16 +1106,19 @@ class PortfoliosTests(TestWithUser, WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user.save()
self.client.force_login(self.user) self.client.force_login(self.user)
self.domain, _ = Domain.objects.get_or_create(name="sampledomain.gov", state=Domain.State.READY) self.domain, _ = Domain.objects.get_or_create(name="sampledomain.gov", state=Domain.State.READY)
self.role, _ = UserDomainRole.objects.get_or_create( self.role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
) )
self.portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="xyz inc") self.federal_agency = FederalAgency.objects.create()
self.portfolio, _ = Portfolio.objects.get_or_create(
creator=self.user, organization_name="xyz inc", federal_agency=self.federal_agency
)
def tearDown(self): def tearDown(self):
Portfolio.objects.all().delete() Portfolio.objects.all().delete()
self.federal_agency.delete()
super().tearDown() super().tearDown()
PublicContact.objects.filter(domain=self.domain).delete() PublicContact.objects.filter(domain=self.domain).delete()
UserDomainRole.objects.all().delete() UserDomainRole.objects.all().delete()

View file

@ -5,6 +5,7 @@ from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from waffle.testutils import override_flag from waffle.testutils import override_flag
from api.tests.common import less_console_noise_decorator
from .common import MockEppLib, MockSESClient, create_user # type: ignore from .common import MockEppLib, MockSESClient, create_user # type: ignore
from django_webtest import WebTest # type: ignore from django_webtest import WebTest # type: ignore
import boto3_mocking # type: ignore import boto3_mocking # type: ignore
@ -48,6 +49,7 @@ logger = logging.getLogger(__name__)
class TestWithDomainPermissions(TestWithUser): class TestWithDomainPermissions(TestWithUser):
@less_console_noise_decorator
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
@ -145,6 +147,7 @@ class TestWithDomainPermissions(TestWithUser):
class TestDomainPermissions(TestWithDomainPermissions): class TestDomainPermissions(TestWithDomainPermissions):
@less_console_noise_decorator
def test_not_logged_in(self): def test_not_logged_in(self):
"""Not logged in gets a redirect to Login.""" """Not logged in gets a redirect to Login."""
for view_name in [ for view_name in [
@ -161,6 +164,7 @@ class TestDomainPermissions(TestWithDomainPermissions):
response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
@less_console_noise_decorator
def test_no_domain_role(self): def test_no_domain_role(self):
"""Logged in but no role gets 403 Forbidden.""" """Logged in but no role gets 403 Forbidden."""
self.client.force_login(self.user) self.client.force_login(self.user)
@ -177,10 +181,10 @@ class TestDomainPermissions(TestWithDomainPermissions):
"domain-security-email", "domain-security-email",
]: ]:
with self.subTest(view_name=view_name): with self.subTest(view_name=view_name):
with less_console_noise(): response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id}))
response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@less_console_noise_decorator
def test_domain_pages_blocked_for_on_hold_and_deleted(self): def test_domain_pages_blocked_for_on_hold_and_deleted(self):
"""Test that the domain pages are blocked for on hold and deleted domains""" """Test that the domain pages are blocked for on hold and deleted domains"""
@ -202,12 +206,12 @@ class TestDomainPermissions(TestWithDomainPermissions):
self.domain_deleted, self.domain_deleted,
]: ]:
with self.subTest(view_name=view_name, domain=domain): with self.subTest(view_name=view_name, domain=domain):
with less_console_noise(): response = self.client.get(reverse(view_name, kwargs={"pk": domain.id}))
response = self.client.get(reverse(view_name, kwargs={"pk": domain.id})) self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 403)
class TestDomainOverview(TestWithDomainPermissions, WebTest): class TestDomainOverview(TestWithDomainPermissions, WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.app.set_user(self.user.username) self.app.set_user(self.user.username)
@ -315,21 +319,25 @@ class TestDomainManagers(TestDomainOverview):
"""Ensure that the user has its original permissions""" """Ensure that the user has its original permissions"""
super().tearDown() super().tearDown()
@less_console_noise_decorator
def test_domain_managers(self): def test_domain_managers(self):
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
self.assertContains(response, "Domain managers") self.assertContains(response, "Domain managers")
@less_console_noise_decorator
def test_domain_managers_add_link(self): def test_domain_managers_add_link(self):
"""Button to get to user add page works.""" """Button to get to user add page works."""
management_page = self.app.get(reverse("domain-users", kwargs={"pk": self.domain.id})) management_page = self.app.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
add_page = management_page.click("Add a domain manager") add_page = management_page.click("Add a domain manager")
self.assertContains(add_page, "Add a domain manager") self.assertContains(add_page, "Add a domain manager")
@less_console_noise_decorator
def test_domain_user_add(self): def test_domain_user_add(self):
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
self.assertContains(response, "Add a domain manager") self.assertContains(response, "Add a domain manager")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_user_add_form(self): def test_domain_user_add_form(self):
"""Adding an existing user works.""" """Adding an existing user works."""
other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov") other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov")
@ -356,6 +364,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertContains(success_page, "mayor@igorville.gov") self.assertContains(success_page, "mayor@igorville.gov")
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_created(self): def test_domain_invitation_created(self):
"""Add user on a nonexistent email creates an invitation. """Add user on a nonexistent email creates an invitation.
@ -386,6 +395,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists())
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_created_for_caps_email(self): def test_domain_invitation_created_for_caps_email(self):
"""Add user on a nonexistent email with CAPS creates an invitation to lowercase email. """Add user on a nonexistent email with CAPS creates an invitation to lowercase email.
@ -406,8 +416,7 @@ class TestDomainManagers(TestDomainOverview):
mock_client = MockSESClient() mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): success_result = add_page.form.submit()
success_result = add_page.form.submit()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_page = success_result.follow() success_page = success_result.follow()
@ -417,6 +426,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists())
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_email_sent(self): def test_domain_invitation_email_sent(self):
"""Inviting a non-existent user sends them an email.""" """Inviting a non-existent user sends them an email."""
# make sure there is no user with this email # make sure there is no user with this email
@ -428,12 +438,11 @@ class TestDomainManagers(TestDomainOverview):
mock_client = MagicMock() mock_client = MagicMock()
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page.form["email"] = email_address
add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit()
add_page.form.submit()
# check the mock instance to see if `send_email` was called right # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -443,6 +452,7 @@ class TestDomainManagers(TestDomainOverview):
) )
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_email_has_email_as_requestor_non_existent(self): def test_domain_invitation_email_has_email_as_requestor_non_existent(self):
"""Inviting a non existent user sends them an email, with email as the name.""" """Inviting a non existent user sends them an email, with email as the name."""
# make sure there is no user with this email # make sure there is no user with this email
@ -455,12 +465,11 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page.form["email"] = email_address
add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit()
add_page.form.submit()
# check the mock instance to see if `send_email` was called right # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -482,6 +491,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertNotIn("First Last", email_content) self.assertNotIn("First Last", email_content)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_email_has_email_as_requestor(self): def test_domain_invitation_email_has_email_as_requestor(self):
"""Inviting a user sends them an email, with email as the name.""" """Inviting a user sends them an email, with email as the name."""
# Create a fake user object # Create a fake user object
@ -494,12 +504,11 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page.form["email"] = email_address
add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit()
add_page.form.submit()
# check the mock instance to see if `send_email` was called right # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -521,6 +530,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertNotIn("First Last", email_content) self.assertNotIn("First Last", email_content)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_email_has_email_as_requestor_staff(self): def test_domain_invitation_email_has_email_as_requestor_staff(self):
"""Inviting a user sends them an email, with email as the name.""" """Inviting a user sends them an email, with email as the name."""
# Create a fake user object # Create a fake user object
@ -537,12 +547,11 @@ class TestDomainManagers(TestDomainOverview):
mock_client_instance = mock_client.return_value mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page.form["email"] = email_address
add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit()
add_page.form.submit()
# check the mock instance to see if `send_email` was called right # check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with( mock_client_instance.send_email.assert_called_once_with(
@ -564,6 +573,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertNotIn("First Last", email_content) self.assertNotIn("First Last", email_content)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_email_displays_error_non_existent(self): def test_domain_invitation_email_displays_error_non_existent(self):
"""Inviting a non existent user sends them an email, with email as the name.""" """Inviting a non existent user sends them an email, with email as the name."""
# make sure there is no user with this email # make sure there is no user with this email
@ -580,12 +590,11 @@ class TestDomainManagers(TestDomainOverview):
mock_error_message = MagicMock() mock_error_message = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch("django.contrib.messages.error") as mock_error_message: with patch("django.contrib.messages.error") as mock_error_message:
with less_console_noise(): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page.form["email"] = email_address
add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit().follow()
add_page.form.submit().follow()
expected_message_content = "Can't send invitation email. No email is associated with your account." expected_message_content = "Can't send invitation email. No email is associated with your account."
@ -596,6 +605,7 @@ class TestDomainManagers(TestDomainOverview):
self.assertEqual(expected_message_content, returned_error_message) self.assertEqual(expected_message_content, returned_error_message)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_email_displays_error(self): def test_domain_invitation_email_displays_error(self):
"""When the requesting user has no email, an error is displayed""" """When the requesting user has no email, an error is displayed"""
# make sure there is no user with this email # make sure there is no user with this email
@ -614,12 +624,11 @@ class TestDomainManagers(TestDomainOverview):
mock_error_message = MagicMock() mock_error_message = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with patch("django.contrib.messages.error") as mock_error_message: with patch("django.contrib.messages.error") as mock_error_message:
with less_console_noise(): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] add_page.form["email"] = email_address
add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit().follow()
add_page.form.submit().follow()
expected_message_content = "Can't send invitation email. No email is associated with your account." expected_message_content = "Can't send invitation email. No email is associated with your account."
@ -629,34 +638,35 @@ class TestDomainManagers(TestDomainOverview):
# Check that the message content is what we expect # Check that the message content is what we expect
self.assertEqual(expected_message_content, returned_error_message) self.assertEqual(expected_message_content, returned_error_message)
@less_console_noise_decorator
def test_domain_invitation_cancel(self): def test_domain_invitation_cancel(self):
"""Posting to the delete view deletes an invitation.""" """Posting to the delete view deletes an invitation."""
email_address = "mayor@igorville.gov" email_address = "mayor@igorville.gov"
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address)
mock_client = MockSESClient() mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
mock_client.EMAILS_SENT.clear() mock_client.EMAILS_SENT.clear()
with self.assertRaises(DomainInvitation.DoesNotExist): with self.assertRaises(DomainInvitation.DoesNotExist):
DomainInvitation.objects.get(id=invitation.id) DomainInvitation.objects.get(id=invitation.id)
@less_console_noise_decorator
def test_domain_invitation_cancel_retrieved_invitation(self): def test_domain_invitation_cancel_retrieved_invitation(self):
"""Posting to the delete view when invitation retrieved returns an error message""" """Posting to the delete view when invitation retrieved returns an error message"""
email_address = "mayor@igorville.gov" email_address = "mayor@igorville.gov"
invitation, _ = DomainInvitation.objects.get_or_create( invitation, _ = DomainInvitation.objects.get_or_create(
domain=self.domain, email=email_address, status=DomainInvitation.DomainInvitationStatus.RETRIEVED domain=self.domain, email=email_address, status=DomainInvitation.DomainInvitationStatus.RETRIEVED
) )
with less_console_noise(): response = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}), follow=True)
response = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}), follow=True) # Assert that an error message is displayed to the user
# Assert that an error message is displayed to the user self.assertContains(response, f"Invitation to {email_address} has already been retrieved.")
self.assertContains(response, f"Invitation to {email_address} has already been retrieved.") # Assert that the Cancel link is not displayed
# Assert that the Cancel link is not displayed self.assertNotContains(response, "Cancel")
self.assertNotContains(response, "Cancel")
# Assert that the DomainInvitation is not deleted # Assert that the DomainInvitation is not deleted
self.assertTrue(DomainInvitation.objects.filter(id=invitation.id).exists()) self.assertTrue(DomainInvitation.objects.filter(id=invitation.id).exists())
DomainInvitation.objects.filter(email=email_address).delete() DomainInvitation.objects.filter(email=email_address).delete()
@less_console_noise_decorator
def test_domain_invitation_cancel_no_permissions(self): def test_domain_invitation_cancel_no_permissions(self):
"""Posting to the delete view as a different user should fail.""" """Posting to the delete view as a different user should fail."""
email_address = "mayor@igorville.gov" email_address = "mayor@igorville.gov"
@ -667,12 +677,12 @@ class TestDomainManagers(TestDomainOverview):
self.client.force_login(other_user) self.client.force_login(other_user)
mock_client = MagicMock() mock_client = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): # permission denied makes console errors result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
result = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
self.assertEqual(result.status_code, 403) self.assertEqual(result.status_code, 403)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_invitation_flow(self): def test_domain_invitation_flow(self):
"""Send an invitation to a new user, log in and load the dashboard.""" """Send an invitation to a new user, log in and load the dashboard."""
email_address = "mayor@igorville.gov" email_address = "mayor@igorville.gov"
@ -688,8 +698,7 @@ class TestDomainManagers(TestDomainOverview):
mock_client = MagicMock() mock_client = MagicMock()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): add_page.form.submit()
add_page.form.submit()
# user was invited, create them # user was invited, create them
new_user = User.objects.create(username=email_address, email=email_address) new_user = User.objects.create(username=email_address, email=email_address)
@ -704,11 +713,13 @@ class TestDomainManagers(TestDomainOverview):
class TestDomainNameservers(TestDomainOverview, MockEppLib): class TestDomainNameservers(TestDomainOverview, MockEppLib):
@less_console_noise_decorator
def test_domain_nameservers(self): def test_domain_nameservers(self):
"""Can load domain's nameservers page.""" """Can load domain's nameservers page."""
page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
self.assertContains(page, "DNS name servers") self.assertContains(page, "DNS name servers")
@less_console_noise_decorator
def test_domain_nameservers_form_submit_one_nameserver(self): def test_domain_nameservers_form_submit_one_nameserver(self):
"""Nameserver form submitted with one nameserver throws error. """Nameserver form submitted with one nameserver throws error.
@ -720,8 +731,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# attempt to submit the form with only one nameserver, should error # attempt to submit the form with only one nameserver, should error
# regarding required fields # regarding required fields
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. form requires a minimum of 2 name servers # the required field. form requires a minimum of 2 name servers
@ -732,6 +742,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submit_subdomain_missing_ip(self): def test_domain_nameservers_form_submit_subdomain_missing_ip(self):
"""Nameserver form catches missing ip error on subdomain. """Nameserver form catches missing ip error on subdomain.
@ -745,8 +756,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# only one has ips # only one has ips
nameservers_page.form["form-1-server"] = "ns2.igorville.gov" nameservers_page.form["form-1-server"] = "ns2.igorville.gov"
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. subdomain missing an ip # the required field. subdomain missing an ip
@ -757,6 +767,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submit_missing_host(self): def test_domain_nameservers_form_submit_missing_host(self):
"""Nameserver form catches error when host is missing. """Nameserver form catches error when host is missing.
@ -769,8 +780,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# attempt to submit the form without two hosts, both subdomains, # attempt to submit the form without two hosts, both subdomains,
# only one has ips # only one has ips
nameservers_page.form["form-1-ip"] = "127.0.0.1" nameservers_page.form["form-1-ip"] = "127.0.0.1"
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. nameserver has ip but missing host # the required field. nameserver has ip but missing host
@ -781,6 +791,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submit_duplicate_host(self): def test_domain_nameservers_form_submit_duplicate_host(self):
"""Nameserver form catches error when host is duplicated. """Nameserver form catches error when host is duplicated.
@ -793,8 +804,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# attempt to submit the form with duplicate host names of fake.host.com # attempt to submit the form with duplicate host names of fake.host.com
nameservers_page.form["form-0-ip"] = "" nameservers_page.form["form-0-ip"] = ""
nameservers_page.form["form-1-server"] = "fake.host.com" nameservers_page.form["form-1-server"] = "fake.host.com"
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. remove duplicate entry # the required field. remove duplicate entry
@ -805,6 +815,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submit_whitespace(self): def test_domain_nameservers_form_submit_whitespace(self):
"""Nameserver form removes whitespace from ip. """Nameserver form removes whitespace from ip.
@ -823,8 +834,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page.form["form-0-ip"] = valid_ip nameservers_page.form["form-0-ip"] = valid_ip
nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-1-ip"] = valid_ip_2
nameservers_page.form["form-1-server"] = nameserver2 nameservers_page.form["form-1-server"] = nameserver2
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an ip address which has been stripped of whitespace, # form submission was a post with an ip address which has been stripped of whitespace,
# response should be a 302 to success page # response should be a 302 to success page
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
@ -838,6 +848,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# with an error message displayed, so need to follow 302 and test for success message # with an error message displayed, so need to follow 302 and test for success message
self.assertContains(page, "The name servers for this domain have been updated") self.assertContains(page, "The name servers for this domain have been updated")
@less_console_noise_decorator
def test_domain_nameservers_form_submit_glue_record_not_allowed(self): def test_domain_nameservers_form_submit_glue_record_not_allowed(self):
"""Nameserver form catches error when IP is present """Nameserver form catches error when IP is present
but host not subdomain. but host not subdomain.
@ -856,8 +867,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page.form["form-0-server"] = nameserver1 nameservers_page.form["form-0-server"] = nameserver1
nameservers_page.form["form-1-server"] = nameserver2 nameservers_page.form["form-1-server"] = nameserver2
nameservers_page.form["form-1-ip"] = valid_ip nameservers_page.form["form-1-ip"] = valid_ip
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. nameserver has ip but missing host # the required field. nameserver has ip but missing host
@ -868,6 +878,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submit_invalid_ip(self): def test_domain_nameservers_form_submit_invalid_ip(self):
"""Nameserver form catches invalid IP on submission. """Nameserver form catches invalid IP on submission.
@ -883,8 +894,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# only one has ips # only one has ips
nameservers_page.form["form-1-server"] = nameserver nameservers_page.form["form-1-server"] = nameserver
nameservers_page.form["form-1-ip"] = invalid_ip nameservers_page.form["form-1-ip"] = invalid_ip
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. nameserver has ip but missing host # the required field. nameserver has ip but missing host
@ -895,6 +905,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submit_invalid_host(self): def test_domain_nameservers_form_submit_invalid_host(self):
"""Nameserver form catches invalid host on submission. """Nameserver form catches invalid host on submission.
@ -910,8 +921,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# only one has ips # only one has ips
nameservers_page.form["form-1-server"] = nameserver nameservers_page.form["form-1-server"] = nameserver
nameservers_page.form["form-1-ip"] = valid_ip nameservers_page.form["form-1-ip"] = valid_ip
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the required field. nameserver has invalid host # the required field. nameserver has invalid host
@ -922,6 +932,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
status_code=200, status_code=200,
) )
@less_console_noise_decorator
def test_domain_nameservers_form_submits_successfully(self): def test_domain_nameservers_form_submits_successfully(self):
"""Nameserver form submits successfully with valid input. """Nameserver form submits successfully with valid input.
@ -938,8 +949,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page.form["form-0-ip"] = valid_ip nameservers_page.form["form-0-ip"] = valid_ip
nameservers_page.form["form-1-server"] = nameserver2 nameservers_page.form["form-1-server"] = nameserver2
nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-1-ip"] = valid_ip_2
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a successful post, response should be a 302 # form submission was a successful post, response should be a 302
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual( self.assertEqual(
@ -950,6 +960,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
page = result.follow() page = result.follow()
self.assertContains(page, "The name servers for this domain have been updated") self.assertContains(page, "The name servers for this domain have been updated")
@less_console_noise_decorator
def test_domain_nameservers_can_blank_out_first_or_second_one_if_enough_entries(self): def test_domain_nameservers_can_blank_out_first_or_second_one_if_enough_entries(self):
"""Nameserver form submits successfully with 2 valid inputs, even if the first or """Nameserver form submits successfully with 2 valid inputs, even if the first or
second entries are blanked out. second entries are blanked out.
@ -972,8 +983,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-1-ip"] = valid_ip_2
nameservers_page.form["form-2-server"] = nameserver3 nameservers_page.form["form-2-server"] = nameserver3
nameservers_page.form["form-2-ip"] = valid_ip_3 nameservers_page.form["form-2-ip"] = valid_ip_3
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a successful post, response should be a 302 # form submission was a successful post, response should be a 302
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
@ -999,8 +1009,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page.form["form-1-ip"] = valid_ip_2 nameservers_page.form["form-1-ip"] = valid_ip_2
nameservers_page.form["form-2-server"] = nameserver3 nameservers_page.form["form-2-server"] = nameserver3
nameservers_page.form["form-2-ip"] = valid_ip_3 nameservers_page.form["form-2-ip"] = valid_ip_3
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a successful post, response should be a 302 # form submission was a successful post, response should be a 302
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
@ -1012,6 +1021,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page = result.follow() nameservers_page = result.follow()
self.assertContains(nameservers_page, "The name servers for this domain have been updated") self.assertContains(nameservers_page, "The name servers for this domain have been updated")
@less_console_noise_decorator
def test_domain_nameservers_can_blank_out_first_and_second_one_if_enough_entries(self): def test_domain_nameservers_can_blank_out_first_and_second_one_if_enough_entries(self):
"""Nameserver form submits successfully with 2 valid inputs, even if the first and """Nameserver form submits successfully with 2 valid inputs, even if the first and
second entries are blanked out. second entries are blanked out.
@ -1048,8 +1058,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page.form["form-2-ip"] = valid_ip_3 nameservers_page.form["form-2-ip"] = valid_ip_3
nameservers_page.form["form-3-server"] = nameserver4 nameservers_page.form["form-3-server"] = nameserver4
nameservers_page.form["form-3-ip"] = valid_ip_4 nameservers_page.form["form-3-ip"] = valid_ip_4
with less_console_noise(): # swallow log warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a successful post, response should be a 302 # form submission was a successful post, response should be a 302
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
@ -1061,6 +1070,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
nameservers_page = result.follow() nameservers_page = result.follow()
self.assertContains(nameservers_page, "The name servers for this domain have been updated") self.assertContains(nameservers_page, "The name servers for this domain have been updated")
@less_console_noise_decorator
def test_domain_nameservers_form_invalid(self): def test_domain_nameservers_form_invalid(self):
"""Nameserver form does not submit with invalid data. """Nameserver form does not submit with invalid data.
@ -1072,8 +1082,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
# first two nameservers are required, so if we empty one out we should # first two nameservers are required, so if we empty one out we should
# get a form error # get a form error
nameservers_page.form["form-0-server"] = "" nameservers_page.form["form-0-server"] = ""
with less_console_noise(): # swallow logged warning message result = nameservers_page.form.submit()
result = nameservers_page.form.submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears four times, twice at the top of the page, # error text appears four times, twice at the top of the page,
# once around each required field. # once around each required field.
@ -1086,11 +1095,13 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib):
class TestDomainSeniorOfficial(TestDomainOverview): class TestDomainSeniorOfficial(TestDomainOverview):
@less_console_noise_decorator
def test_domain_senior_official(self): def test_domain_senior_official(self):
"""Can load domain's senior official page.""" """Can load domain's senior official page."""
page = self.client.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id})) page = self.client.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id}))
self.assertContains(page, "Senior official", count=14) self.assertContains(page, "Senior official", count=14)
@less_console_noise_decorator
def test_domain_senior_official_content(self): def test_domain_senior_official_content(self):
"""Senior official information appears on the page.""" """Senior official information appears on the page."""
self.domain_information.senior_official = Contact(first_name="Testy") self.domain_information.senior_official = Contact(first_name="Testy")
@ -1143,6 +1154,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.assertNotContains(page, "Testy") self.assertNotContains(page, "Testy")
self.assertContains(page, "Bob") self.assertContains(page, "Bob")
@less_console_noise_decorator
def test_domain_edit_senior_official_in_place(self): def test_domain_edit_senior_official_in_place(self):
"""When editing a senior official for domain information and SO is not """When editing a senior official for domain information and SO is not
joined to any other objects""" joined to any other objects"""
@ -1167,6 +1179,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.assertEqual("Testy2", self.domain_information.senior_official.first_name) self.assertEqual("Testy2", self.domain_information.senior_official.first_name)
self.assertEqual(so_pk, self.domain_information.senior_official.id) self.assertEqual(so_pk, self.domain_information.senior_official.id)
@less_console_noise_decorator
def assert_all_form_fields_have_expected_values(self, form, test_cases, test_for_disabled=False): def assert_all_form_fields_have_expected_values(self, form, test_cases, test_for_disabled=False):
""" """
Asserts that each specified form field has the expected value and, optionally, checks if the field is disabled. Asserts that each specified form field has the expected value and, optionally, checks if the field is disabled.
@ -1193,6 +1206,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
# Test for disabled on each field # Test for disabled on each field
self.assertTrue("disabled" in form[field_name].attrs) self.assertTrue("disabled" in form[field_name].attrs)
@less_console_noise_decorator
def test_domain_edit_senior_official_federal(self): def test_domain_edit_senior_official_federal(self):
"""Tests that no edit can occur when the underlying domain is federal""" """Tests that no edit can occur when the underlying domain is federal"""
@ -1249,6 +1263,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.assertEqual("CIO", self.domain_information.senior_official.title) self.assertEqual("CIO", self.domain_information.senior_official.title)
self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email) self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email)
@less_console_noise_decorator
def test_domain_edit_senior_official_tribal(self): def test_domain_edit_senior_official_tribal(self):
"""Tests that no edit can occur when the underlying domain is tribal""" """Tests that no edit can occur when the underlying domain is tribal"""
@ -1305,6 +1320,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.assertEqual("CIO", self.domain_information.senior_official.title) self.assertEqual("CIO", self.domain_information.senior_official.title)
self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email) self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email)
@less_console_noise_decorator
def test_domain_edit_senior_official_creates_new(self): def test_domain_edit_senior_official_creates_new(self):
"""When editing a senior official for domain information and SO IS """When editing a senior official for domain information and SO IS
joined to another object""" joined to another object"""
@ -1342,12 +1358,14 @@ class TestDomainSeniorOfficial(TestDomainOverview):
class TestDomainOrganization(TestDomainOverview): class TestDomainOrganization(TestDomainOverview):
@less_console_noise_decorator
def test_domain_org_name_address(self): def test_domain_org_name_address(self):
"""Can load domain's org name and mailing address page.""" """Can load domain's org name and mailing address page."""
page = self.client.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) page = self.client.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id}))
# once on the sidebar, once in the page title, once as H1 # once on the sidebar, once in the page title, once as H1
self.assertContains(page, "Organization name and mailing address", count=4) self.assertContains(page, "Organization name and mailing address", count=4)
@less_console_noise_decorator
def test_domain_org_name_address_content(self): def test_domain_org_name_address_content(self):
"""Org name and address information appears on the page.""" """Org name and address information appears on the page."""
self.domain_information.organization_name = "Town of Igorville" self.domain_information.organization_name = "Town of Igorville"
@ -1355,6 +1373,7 @@ class TestDomainOrganization(TestDomainOverview):
page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id}))
self.assertContains(page, "Town of Igorville") self.assertContains(page, "Town of Igorville")
@less_console_noise_decorator
def test_domain_org_name_address_form(self): def test_domain_org_name_address_form(self):
"""Submitting changes works on the org name address page.""" """Submitting changes works on the org name address page."""
self.domain_information.organization_name = "Town of Igorville" self.domain_information.organization_name = "Town of Igorville"
@ -1372,6 +1391,7 @@ class TestDomainOrganization(TestDomainOverview):
self.assertContains(success_result_page, "Not igorville") self.assertContains(success_result_page, "Not igorville")
self.assertContains(success_result_page, "Faketown") self.assertContains(success_result_page, "Faketown")
@less_console_noise_decorator
def test_domain_org_name_address_form_tribal(self): def test_domain_org_name_address_form_tribal(self):
""" """
Submitting a change to organization_name is blocked for tribal domains Submitting a change to organization_name is blocked for tribal domains
@ -1429,6 +1449,7 @@ class TestDomainOrganization(TestDomainOverview):
# Check for the value we want to update # Check for the value we want to update
self.assertContains(success_result_page, "Faketown") self.assertContains(success_result_page, "Faketown")
@less_console_noise_decorator
def test_domain_org_name_address_form_federal(self): def test_domain_org_name_address_form_federal(self):
""" """
Submitting a change to federal_agency is blocked for federal domains Submitting a change to federal_agency is blocked for federal domains
@ -1484,6 +1505,7 @@ class TestDomainOrganization(TestDomainOverview):
# Check for the value we want to update # Check for the value we want to update
self.assertContains(success_result_page, "Faketown") self.assertContains(success_result_page, "Faketown")
@less_console_noise_decorator
def test_federal_agency_submit_blocked(self): def test_federal_agency_submit_blocked(self):
""" """
Submitting a change to federal_agency is blocked for federal domains Submitting a change to federal_agency is blocked for federal domains
@ -1517,11 +1539,13 @@ class TestDomainOrganization(TestDomainOverview):
class TestDomainContactInformation(TestDomainOverview): class TestDomainContactInformation(TestDomainOverview):
@less_console_noise_decorator
def test_domain_your_contact_information(self): def test_domain_your_contact_information(self):
"""Can load domain's your contact information page.""" """Can load domain's your contact information page."""
page = self.client.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})) page = self.client.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id}))
self.assertContains(page, "Your contact information") self.assertContains(page, "Your contact information")
@less_console_noise_decorator
def test_domain_your_contact_information_content(self): def test_domain_your_contact_information_content(self):
"""Logged-in user's contact information appears on the page.""" """Logged-in user's contact information appears on the page."""
self.user.first_name = "Testy" self.user.first_name = "Testy"
@ -1649,20 +1673,21 @@ class TestDomainSecurityEmail(TestDomainOverview):
self.assertEqual(message.tags, message_tag) self.assertEqual(message.tags, message_tag)
self.assertEqual(message.message.strip(), expected_message.strip()) self.assertEqual(message.message.strip(), expected_message.strip())
@less_console_noise_decorator
def test_domain_overview_blocked_for_ineligible_user(self): def test_domain_overview_blocked_for_ineligible_user(self):
"""We could easily duplicate this test for all domain management """We could easily duplicate this test for all domain management
views, but a single url test should be solid enough since all domain views, but a single url test should be solid enough since all domain
management pages share the same permissions class""" management pages share the same permissions class"""
self.user.status = User.RESTRICTED self.user.status = User.RESTRICTED
self.user.save() self.user.save()
with less_console_noise(): response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 403)
class TestDomainDNSSEC(TestDomainOverview): class TestDomainDNSSEC(TestDomainOverview):
"""MockEPPLib is already inherited.""" """MockEPPLib is already inherited."""
@less_console_noise_decorator
def test_dnssec_page_refreshes_enable_button(self): def test_dnssec_page_refreshes_enable_button(self):
"""DNSSEC overview page loads when domain has no DNSSEC data """DNSSEC overview page loads when domain has no DNSSEC data
and shows a 'Enable DNSSEC' button.""" and shows a 'Enable DNSSEC' button."""
@ -1670,6 +1695,7 @@ class TestDomainDNSSEC(TestDomainOverview):
page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})) page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}))
self.assertContains(page, "Enable DNSSEC") self.assertContains(page, "Enable DNSSEC")
@less_console_noise_decorator
def test_dnssec_page_loads_with_data_in_domain(self): def test_dnssec_page_loads_with_data_in_domain(self):
"""DNSSEC overview page loads when domain has DNSSEC data """DNSSEC overview page loads when domain has DNSSEC data
and the template contains a button to disable DNSSEC.""" and the template contains a button to disable DNSSEC."""
@ -1691,6 +1717,7 @@ class TestDomainDNSSEC(TestDomainOverview):
self.assertContains(updated_page, "Enable DNSSEC") self.assertContains(updated_page, "Enable DNSSEC")
@less_console_noise_decorator
def test_ds_form_loads_with_no_domain_data(self): def test_ds_form_loads_with_no_domain_data(self):
"""DNSSEC Add DS data page loads when there is no """DNSSEC Add DS data page loads when there is no
domain DNSSEC data and shows a button to Add new record""" domain DNSSEC data and shows a button to Add new record"""
@ -1699,6 +1726,7 @@ class TestDomainDNSSEC(TestDomainOverview):
self.assertContains(page, "You have no DS data added") self.assertContains(page, "You have no DS data added")
self.assertContains(page, "Add new record") self.assertContains(page, "Add new record")
@less_console_noise_decorator
def test_ds_form_loads_with_ds_data(self): def test_ds_form_loads_with_ds_data(self):
"""DNSSEC Add DS data page loads when there is """DNSSEC Add DS data page loads when there is
domain DNSSEC DS data and shows the data""" domain DNSSEC DS data and shows the data"""
@ -1706,6 +1734,7 @@ class TestDomainDNSSEC(TestDomainOverview):
page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
self.assertContains(page, "DS data record 1") self.assertContains(page, "DS data record 1")
@less_console_noise_decorator
def test_ds_data_form_modal(self): def test_ds_data_form_modal(self):
"""When user clicks on save, a modal pops up.""" """When user clicks on save, a modal pops up."""
add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
@ -1724,6 +1753,7 @@ class TestDomainDNSSEC(TestDomainOverview):
# Now check to see whether the JS trigger for the modal is present on the page # Now check to see whether the JS trigger for the modal is present on the page
self.assertContains(response, "Trigger Disable DNSSEC Modal") self.assertContains(response, "Trigger Disable DNSSEC Modal")
@less_console_noise_decorator
def test_ds_data_form_submits(self): def test_ds_data_form_submits(self):
"""DS data form submits successfully """DS data form submits successfully
@ -1732,8 +1762,7 @@ class TestDomainDNSSEC(TestDomainOverview):
add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) add_data_page = self.app.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
with less_console_noise(): # swallow log warning message result = add_data_page.forms[0].submit()
result = add_data_page.forms[0].submit()
# form submission was a post, response should be a redirect # form submission was a post, response should be a redirect
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual( self.assertEqual(
@ -1744,6 +1773,7 @@ class TestDomainDNSSEC(TestDomainOverview):
page = result.follow() page = result.follow()
self.assertContains(page, "The DS data records for this domain have been updated.") self.assertContains(page, "The DS data records for this domain have been updated.")
@less_console_noise_decorator
def test_ds_data_form_invalid(self): def test_ds_data_form_invalid(self):
"""DS data form errors with invalid data (missing required fields) """DS data form errors with invalid data (missing required fields)
@ -1757,8 +1787,7 @@ class TestDomainDNSSEC(TestDomainOverview):
add_data_page.forms[0]["form-0-algorithm"] = "" add_data_page.forms[0]["form-0-algorithm"] = ""
add_data_page.forms[0]["form-0-digest_type"] = "" add_data_page.forms[0]["form-0-digest_type"] = ""
add_data_page.forms[0]["form-0-digest"] = "" add_data_page.forms[0]["form-0-digest"] = ""
with less_console_noise(): # swallow logged warning message result = add_data_page.forms[0].submit()
result = add_data_page.forms[0].submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the field. # the field.
@ -1767,6 +1796,7 @@ class TestDomainDNSSEC(TestDomainOverview):
self.assertContains(result, "Digest type is required", count=2, status_code=200) self.assertContains(result, "Digest type is required", count=2, status_code=200)
self.assertContains(result, "Digest is required", count=2, status_code=200) self.assertContains(result, "Digest is required", count=2, status_code=200)
@less_console_noise_decorator
def test_ds_data_form_invalid_keytag(self): def test_ds_data_form_invalid_keytag(self):
"""DS data form errors with invalid data (key tag too large) """DS data form errors with invalid data (key tag too large)
@ -1781,8 +1811,7 @@ class TestDomainDNSSEC(TestDomainOverview):
add_data_page.forms[0]["form-0-algorithm"] = "" add_data_page.forms[0]["form-0-algorithm"] = ""
add_data_page.forms[0]["form-0-digest_type"] = "" add_data_page.forms[0]["form-0-digest_type"] = ""
add_data_page.forms[0]["form-0-digest"] = "" add_data_page.forms[0]["form-0-digest"] = ""
with less_console_noise(): # swallow logged warning message result = add_data_page.forms[0].submit()
result = add_data_page.forms[0].submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the field. # the field.
@ -1790,6 +1819,7 @@ class TestDomainDNSSEC(TestDomainOverview):
result, str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE)), count=2, status_code=200 result, str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE)), count=2, status_code=200
) )
@less_console_noise_decorator
def test_ds_data_form_invalid_digest_chars(self): def test_ds_data_form_invalid_digest_chars(self):
"""DS data form errors with invalid data (digest contains non hexadecimal chars) """DS data form errors with invalid data (digest contains non hexadecimal chars)
@ -1804,8 +1834,7 @@ class TestDomainDNSSEC(TestDomainOverview):
add_data_page.forms[0]["form-0-algorithm"] = "3" add_data_page.forms[0]["form-0-algorithm"] = "3"
add_data_page.forms[0]["form-0-digest_type"] = "1" add_data_page.forms[0]["form-0-digest_type"] = "1"
add_data_page.forms[0]["form-0-digest"] = "GG1234" add_data_page.forms[0]["form-0-digest"] = "GG1234"
with less_console_noise(): # swallow logged warning message result = add_data_page.forms[0].submit()
result = add_data_page.forms[0].submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the field. # the field.
@ -1813,6 +1842,7 @@ class TestDomainDNSSEC(TestDomainOverview):
result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)), count=2, status_code=200 result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)), count=2, status_code=200
) )
@less_console_noise_decorator
def test_ds_data_form_invalid_digest_sha1(self): def test_ds_data_form_invalid_digest_sha1(self):
"""DS data form errors with invalid data (digest is invalid sha-1) """DS data form errors with invalid data (digest is invalid sha-1)
@ -1827,8 +1857,7 @@ class TestDomainDNSSEC(TestDomainOverview):
add_data_page.forms[0]["form-0-algorithm"] = "3" add_data_page.forms[0]["form-0-algorithm"] = "3"
add_data_page.forms[0]["form-0-digest_type"] = "1" # SHA-1 add_data_page.forms[0]["form-0-digest_type"] = "1" # SHA-1
add_data_page.forms[0]["form-0-digest"] = "A123" add_data_page.forms[0]["form-0-digest"] = "A123"
with less_console_noise(): # swallow logged warning message result = add_data_page.forms[0].submit()
result = add_data_page.forms[0].submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the field. # the field.
@ -1836,6 +1865,7 @@ class TestDomainDNSSEC(TestDomainOverview):
result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1)), count=2, status_code=200 result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1)), count=2, status_code=200
) )
@less_console_noise_decorator
def test_ds_data_form_invalid_digest_sha256(self): def test_ds_data_form_invalid_digest_sha256(self):
"""DS data form errors with invalid data (digest is invalid sha-256) """DS data form errors with invalid data (digest is invalid sha-256)
@ -1850,8 +1880,7 @@ class TestDomainDNSSEC(TestDomainOverview):
add_data_page.forms[0]["form-0-algorithm"] = "3" add_data_page.forms[0]["form-0-algorithm"] = "3"
add_data_page.forms[0]["form-0-digest_type"] = "2" # SHA-256 add_data_page.forms[0]["form-0-digest_type"] = "2" # SHA-256
add_data_page.forms[0]["form-0-digest"] = "GG1234" add_data_page.forms[0]["form-0-digest"] = "GG1234"
with less_console_noise(): # swallow logged warning message result = add_data_page.forms[0].submit()
result = add_data_page.forms[0].submit()
# form submission was a post with an error, response should be a 200 # form submission was a post with an error, response should be a 200
# error text appears twice, once at the top of the page, once around # error text appears twice, once at the top of the page, once around
# the field. # the field.

View file

@ -24,7 +24,6 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
UserDomainRole.objects.all().delete() UserDomainRole.objects.all().delete()
UserDomainRole.objects.all().delete()
@less_console_noise_decorator @less_console_noise_decorator
def test_get_domains_json_unauthenticated(self): def test_get_domains_json_unauthenticated(self):

View file

@ -1,5 +1,6 @@
from django.urls import reverse from django.urls import reverse
from api.tests.common import less_console_noise_decorator from api.tests.common import less_console_noise_decorator
from registrar.config import settings
from registrar.models.portfolio import Portfolio from registrar.models.portfolio import Portfolio
from django_webtest import WebTest # type: ignore from django_webtest import WebTest # type: ignore
from registrar.models import ( from registrar.models import (
@ -9,7 +10,7 @@ from registrar.models import (
UserDomainRole, UserDomainRole,
User, User,
) )
from .test_views import TestWithUser from .common import create_test_user
from waffle.testutils import override_flag from waffle.testutils import override_flag
import logging import logging
@ -17,15 +18,25 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TestPortfolioViews(TestWithUser, WebTest): class TestPortfolio(WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = create_test_user()
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
self.portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California") self.portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California")
self.role, _ = UserDomainRole.objects.get_or_create( self.role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
) )
def tearDown(self):
Portfolio.objects.all().delete()
UserDomainRole.objects.all().delete()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
Domain.objects.all().delete()
User.objects.all().delete()
super().tearDown()
@less_console_noise_decorator @less_console_noise_decorator
def test_middleware_does_not_redirect_if_no_permission(self): def test_middleware_does_not_redirect_if_no_permission(self):
"""Test that user with no portfolio permission is not redirected when attempting to access home""" """Test that user with no portfolio permission is not redirected when attempting to access home"""
@ -183,10 +194,69 @@ class TestPortfolioViews(TestWithUser, WebTest):
portfolio_page, reverse("portfolio-domain-requests", kwargs={"portfolio_id": self.portfolio.pk}) portfolio_page, reverse("portfolio-domain-requests", kwargs={"portfolio_id": self.portfolio.pk})
) )
def tearDown(self):
Portfolio.objects.all().delete() class TestPortfolioOrganization(TestPortfolio):
UserDomainRole.objects.all().delete()
DomainRequest.objects.all().delete() def test_portfolio_org_name(self):
DomainInformation.objects.all().delete() """Can load portfolio's org name page."""
Domain.objects.all().delete() with override_flag("organization_feature", active=True):
super().tearDown() self.app.set_user(self.user.username)
self.user.portfolio = self.portfolio
self.user.portfolio_additional_permissions = [
User.UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
User.UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
]
self.user.save()
self.user.refresh_from_db()
page = self.app.get(reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}))
self.assertContains(
page, "The name of your federal agency will be publicly listed as the domain registrant."
)
def test_domain_org_name_address_content(self):
"""Org name and address information appears on the page."""
with override_flag("organization_feature", active=True):
self.app.set_user(self.user.username)
self.user.portfolio = self.portfolio
self.user.portfolio_additional_permissions = [
User.UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
User.UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
]
self.user.save()
self.user.refresh_from_db()
self.portfolio.organization_name = "Hotel California"
self.portfolio.save()
page = self.app.get(reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}))
# Once in the sidenav, once in the main nav, once in the form
self.assertContains(page, "Hotel California", count=3)
def test_domain_org_name_address_form(self):
"""Submitting changes works on the org name address page."""
with override_flag("organization_feature", active=True):
self.app.set_user(self.user.username)
self.user.portfolio = self.portfolio
self.user.portfolio_additional_permissions = [
User.UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
User.UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
]
self.user.save()
self.user.refresh_from_db()
self.portfolio.address_line1 = "1600 Penn Ave"
self.portfolio.save()
portfolio_org_name_page = self.app.get(
reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk})
)
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
portfolio_org_name_page.form["address_line1"] = "6 Downing st"
portfolio_org_name_page.form["city"] = "London"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_result_page = portfolio_org_name_page.form.submit()
self.assertEqual(success_result_page.status_code, 200)
self.assertContains(success_result_page, "6 Downing st")
self.assertContains(success_result_page, "London")

View file

@ -3,7 +3,7 @@ from unittest.mock import Mock
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
from api.tests.common import less_console_noise_decorator
from .common import MockSESClient, completed_domain_request # type: ignore from .common import MockSESClient, completed_domain_request # type: ignore
from django_webtest import WebTest # type: ignore from django_webtest import WebTest # type: ignore
import boto3_mocking # type: ignore import boto3_mocking # type: ignore
@ -37,14 +37,23 @@ class DomainRequestTests(TestWithUser, WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
self.app.set_user(self.user.username) self.app.set_user(self.user.username)
self.TITLES = DomainRequestWizard.TITLES self.TITLES = DomainRequestWizard.TITLES
def tearDown(self):
super().tearDown()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
self.federal_agency.delete()
@less_console_noise_decorator
def test_domain_request_form_intro_acknowledgement(self): def test_domain_request_form_intro_acknowledgement(self):
"""Tests that user is presented with intro acknowledgement page""" """Tests that user is presented with intro acknowledgement page"""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
self.assertContains(intro_page, "Youre about to start your .gov domain request") self.assertContains(intro_page, "Youre about to start your .gov domain request")
@less_console_noise_decorator
def test_domain_request_form_intro_is_skipped_when_edit_access(self): def test_domain_request_form_intro_is_skipped_when_edit_access(self):
"""Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'""" """Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'"""
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user) domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
@ -55,6 +64,7 @@ class DomainRequestTests(TestWithUser, WebTest):
redirect_url = detail_page.url redirect_url = detail_page.url
self.assertEqual(redirect_url, "/request/generic_org_type/") self.assertEqual(redirect_url, "/request/generic_org_type/")
@less_console_noise_decorator
def test_domain_request_form_empty_submit(self): def test_domain_request_form_empty_submit(self):
"""Tests empty submit on the first page after the acknowledgement page""" """Tests empty submit on the first page after the acknowledgement page"""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -77,31 +87,31 @@ class DomainRequestTests(TestWithUser, WebTest):
result = type_page.forms[0].submit() result = type_page.forms[0].submit()
self.assertIn("What kind of U.S.-based government organization do you represent?", result) self.assertIn("What kind of U.S.-based government organization do you represent?", result)
@less_console_noise_decorator
def test_domain_request_multiple_domain_requests_exist(self): def test_domain_request_multiple_domain_requests_exist(self):
"""Test that an info message appears when user has multiple domain requests already""" """Test that an info message appears when user has multiple domain requests already"""
# create and submit a domain request # create and submit a domain request
domain_request = completed_domain_request(user=self.user) domain_request = completed_domain_request(user=self.user)
mock_client = MockSESClient() mock_client = MockSESClient()
with boto3_mocking.clients.handler_for("sesv2", mock_client): with boto3_mocking.clients.handler_for("sesv2", mock_client):
with less_console_noise(): domain_request.submit()
domain_request.submit() domain_request.save()
domain_request.save()
# now, attempt to create another one # now, attempt to create another one
with less_console_noise(): intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:")) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] intro_form = intro_page.forms[0]
intro_form = intro_page.forms[0] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) intro_result = intro_form.submit()
intro_result = intro_form.submit()
# follow first redirect # follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow() type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.assertContains(type_page, "You cannot submit this request yet") self.assertContains(type_page, "You cannot submit this request yet")
@less_console_noise_decorator
def test_domain_request_into_acknowledgement_creates_new_request(self): def test_domain_request_into_acknowledgement_creates_new_request(self):
""" """
We had to solve a bug where the wizard was creating 2 requests on first intro acknowledgement ('continue') We had to solve a bug where the wizard was creating 2 requests on first intro acknowledgement ('continue')
@ -155,6 +165,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(domain_request_count, 2) self.assertEqual(domain_request_count, 2)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_request_form_submission(self): def test_domain_request_form_submission(self):
""" """
Can fill out the entire form and submit. Can fill out the entire form and submit.
@ -227,9 +238,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_page = federal_result.follow() org_contact_page = federal_result.follow()
org_contact_form = org_contact_page.forms[0] org_contact_form = org_contact_page.forms[0]
# federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = self.federal_agency.id
federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
org_contact_form["organization_contact-federal_agency"] = federal_agency.id
org_contact_form["organization_contact-organization_name"] = "Testorg" org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1" org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-address_line2"] = "address 2" org_contact_form["organization_contact-address_line2"] = "address 2"
@ -524,6 +533,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(num_pages, num_pages_tested) self.assertEqual(num_pages, num_pages_tested)
@boto3_mocking.patching @boto3_mocking.patching
@less_console_noise_decorator
def test_domain_request_form_submission_incomplete(self): def test_domain_request_form_submission_incomplete(self):
num_pages_tested = 0 num_pages_tested = 0
# skipping elections, type_of_work, tribal_government # skipping elections, type_of_work, tribal_government
@ -584,9 +594,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_page = federal_result.follow() org_contact_page = federal_result.follow()
org_contact_form = org_contact_page.forms[0] org_contact_form = org_contact_page.forms[0]
# federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = self.federal_agency.id
federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
org_contact_form["organization_contact-federal_agency"] = federal_agency.id
org_contact_form["organization_contact-organization_name"] = "Testorg" org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1" org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-address_line2"] = "address 2" org_contact_form["organization_contact-address_line2"] = "address 2"
@ -879,6 +887,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(num_pages, num_pages_tested) self.assertEqual(num_pages, num_pages_tested)
@less_console_noise_decorator
def test_domain_request_form_conditional_federal(self): def test_domain_request_form_conditional_federal(self):
"""Federal branch question is shown for federal organizations.""" """Federal branch question is shown for federal organizations."""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -934,6 +943,7 @@ class DomainRequestTests(TestWithUser, WebTest):
contact_page = federal_result.follow() contact_page = federal_result.follow()
self.assertContains(contact_page, "Federal agency") self.assertContains(contact_page, "Federal agency")
@less_console_noise_decorator
def test_domain_request_form_conditional_elections(self): def test_domain_request_form_conditional_elections(self):
"""Election question is shown for other organizations.""" """Election question is shown for other organizations."""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -988,6 +998,7 @@ class DomainRequestTests(TestWithUser, WebTest):
contact_page = election_result.follow() contact_page = election_result.follow()
self.assertNotContains(contact_page, "Federal agency") self.assertNotContains(contact_page, "Federal agency")
@less_console_noise_decorator
def test_domain_request_form_section_skipping(self): def test_domain_request_form_section_skipping(self):
"""Can skip forward and back in sections""" """Can skip forward and back in sections"""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -1025,6 +1036,7 @@ class DomainRequestTests(TestWithUser, WebTest):
0, 0,
) )
@less_console_noise_decorator
def test_domain_request_form_nonfederal(self): def test_domain_request_form_nonfederal(self):
"""Non-federal organizations don't have to provide their federal agency.""" """Non-federal organizations don't have to provide their federal agency."""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -1069,6 +1081,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(contact_result.status_code, 302) self.assertEqual(contact_result.status_code, 302)
self.assertEqual(contact_result["Location"], "/request/about_your_organization/") self.assertEqual(contact_result["Location"], "/request/about_your_organization/")
@less_console_noise_decorator
def test_domain_request_about_your_organization_special(self): def test_domain_request_about_your_organization_special(self):
"""Special districts have to answer an additional question.""" """Special districts have to answer an additional question."""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -1097,6 +1110,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION])
@less_console_noise_decorator
def test_federal_agency_dropdown_excludes_expected_values(self): def test_federal_agency_dropdown_excludes_expected_values(self):
"""The Federal Agency dropdown on a domain request form should not """The Federal Agency dropdown on a domain request form should not
include options for gov Administration and Non-Federal Agency""" include options for gov Administration and Non-Federal Agency"""
@ -1144,6 +1158,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# make sure correct federal agency options still show up # make sure correct federal agency options still show up
self.assertContains(org_contact_page, "General Services Administration") self.assertContains(org_contact_page, "General Services Administration")
@less_console_noise_decorator
def test_yes_no_contact_form_inits_blank_for_new_domain_request(self): def test_yes_no_contact_form_inits_blank_for_new_domain_request(self):
"""On the Other Contacts page, the yes/no form gets initialized with nothing selected for """On the Other Contacts page, the yes/no form gets initialized with nothing selected for
new domain requests""" new domain requests"""
@ -1151,6 +1166,7 @@ class DomainRequestTests(TestWithUser, WebTest):
other_contacts_form = other_contacts_page.forms[0] other_contacts_form = other_contacts_page.forms[0]
self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, None) self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, None)
@less_console_noise_decorator
def test_yes_no_additional_form_inits_blank_for_new_domain_request(self): def test_yes_no_additional_form_inits_blank_for_new_domain_request(self):
"""On the Additional Details page, the yes/no form gets initialized with nothing selected for """On the Additional Details page, the yes/no form gets initialized with nothing selected for
new domain requests""" new domain requests"""
@ -1163,6 +1179,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# Check the anything else yes/no field # Check the anything else yes/no field
self.assertEquals(additional_form["additional_details-has_anything_else_text"].value, None) self.assertEquals(additional_form["additional_details-has_anything_else_text"].value, None)
@less_console_noise_decorator
def test_yes_no_form_inits_yes_for_domain_request_with_other_contacts(self): def test_yes_no_form_inits_yes_for_domain_request_with_other_contacts(self):
"""On the Other Contacts page, the yes/no form gets initialized with YES selected if the """On the Other Contacts page, the yes/no form gets initialized with YES selected if the
domain request has other contacts""" domain request has other contacts"""
@ -1183,6 +1200,7 @@ class DomainRequestTests(TestWithUser, WebTest):
other_contacts_form = other_contacts_page.forms[0] other_contacts_form = other_contacts_page.forms[0]
self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True")
@less_console_noise_decorator
def test_yes_no_form_inits_yes_for_cisa_representative_and_anything_else(self): def test_yes_no_form_inits_yes_for_cisa_representative_and_anything_else(self):
"""On the Additional Details page, the yes/no form gets initialized with YES selected """On the Additional Details page, the yes/no form gets initialized with YES selected
for both yes/no radios if the domain request has a values for cisa_representative_first_name and for both yes/no radios if the domain request has a values for cisa_representative_first_name and
@ -1214,6 +1232,7 @@ class DomainRequestTests(TestWithUser, WebTest):
yes_no_anything_else = additional_details_form["additional_details-has_anything_else_text"].value yes_no_anything_else = additional_details_form["additional_details-has_anything_else_text"].value
self.assertEquals(yes_no_anything_else, "True") self.assertEquals(yes_no_anything_else, "True")
@less_console_noise_decorator
def test_yes_no_form_inits_no_for_domain_request_with_no_other_contacts_rationale(self): def test_yes_no_form_inits_no_for_domain_request_with_no_other_contacts_rationale(self):
"""On the Other Contacts page, the yes/no form gets initialized with NO selected if the """On the Other Contacts page, the yes/no form gets initialized with NO selected if the
domain request has no other contacts""" domain request has no other contacts"""
@ -1236,6 +1255,7 @@ class DomainRequestTests(TestWithUser, WebTest):
other_contacts_form = other_contacts_page.forms[0] other_contacts_form = other_contacts_page.forms[0]
self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False") self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False")
@less_console_noise_decorator
def test_yes_no_form_for_domain_request_with_no_cisa_representative_and_anything_else(self): def test_yes_no_form_for_domain_request_with_no_cisa_representative_and_anything_else(self):
"""On the Additional details page, the form preselects "no" when has_cisa_representative """On the Additional details page, the form preselects "no" when has_cisa_representative
and anything_else is no""" and anything_else is no"""
@ -1271,6 +1291,7 @@ class DomainRequestTests(TestWithUser, WebTest):
yes_no_anything_else = additional_details_form["additional_details-has_anything_else_text"].value yes_no_anything_else = additional_details_form["additional_details-has_anything_else_text"].value
self.assertEquals(yes_no_anything_else, "False") self.assertEquals(yes_no_anything_else, "False")
@less_console_noise_decorator
def test_submitting_additional_details_deletes_cisa_representative_and_anything_else(self): def test_submitting_additional_details_deletes_cisa_representative_and_anything_else(self):
"""When a user submits the Additional Details form with no selected for all fields, """When a user submits the Additional Details form with no selected for all fields,
the domain request's data gets wiped when submitted""" the domain request's data gets wiped when submitted"""
@ -1332,6 +1353,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(domain_request.cisa_representative_last_name, None) self.assertEqual(domain_request.cisa_representative_last_name, None)
self.assertEqual(domain_request.cisa_representative_email, None) self.assertEqual(domain_request.cisa_representative_email, None)
@less_console_noise_decorator
def test_submitting_additional_details_populates_cisa_representative_and_anything_else(self): def test_submitting_additional_details_populates_cisa_representative_and_anything_else(self):
"""When a user submits the Additional Details form, """When a user submits the Additional Details form,
the domain request's data gets submitted""" the domain request's data gets submitted"""
@ -1385,6 +1407,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(domain_request.has_cisa_representative, True) self.assertEqual(domain_request.has_cisa_representative, True)
self.assertEqual(domain_request.has_anything_else_text, True) self.assertEqual(domain_request.has_anything_else_text, True)
@less_console_noise_decorator
def test_if_cisa_representative_yes_no_form_is_yes_then_field_is_required(self): def test_if_cisa_representative_yes_no_form_is_yes_then_field_is_required(self):
"""Applicants with a cisa representative must provide a value""" """Applicants with a cisa representative must provide a value"""
domain_request = completed_domain_request( domain_request = completed_domain_request(
@ -1417,6 +1440,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(response, "Enter the first name / given name of the CISA regional representative.") self.assertContains(response, "Enter the first name / given name of the CISA regional representative.")
self.assertContains(response, "Enter the last name / family name of the CISA regional representative.") self.assertContains(response, "Enter the last name / family name of the CISA regional representative.")
@less_console_noise_decorator
def test_if_anything_else_yes_no_form_is_yes_then_field_is_required(self): def test_if_anything_else_yes_no_form_is_yes_then_field_is_required(self):
"""Applicants with a anything else must provide a value""" """Applicants with a anything else must provide a value"""
domain_request = completed_domain_request(name="cisareps.gov", user=self.user, has_anything_else=False) domain_request = completed_domain_request(name="cisareps.gov", user=self.user, has_anything_else=False)
@ -1447,6 +1471,7 @@ class DomainRequestTests(TestWithUser, WebTest):
expected_message = "Provide additional details youd like us to know. If you have nothing to add, select “No.”" expected_message = "Provide additional details youd like us to know. If you have nothing to add, select “No.”"
self.assertContains(response, expected_message) self.assertContains(response, expected_message)
@less_console_noise_decorator
def test_additional_details_form_fields_required(self): def test_additional_details_form_fields_required(self):
"""When a user submits the Additional Details form without checking the """When a user submits the Additional Details form without checking the
has_cisa_representative and has_anything_else_text fields, the form should deny this action""" has_cisa_representative and has_anything_else_text fields, the form should deny this action"""
@ -1480,6 +1505,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# due to screen reader information / html. # due to screen reader information / html.
self.assertContains(response, "This question is required.", count=4) self.assertContains(response, "This question is required.", count=4)
@less_console_noise_decorator
def test_submitting_other_contacts_deletes_no_other_contacts_rationale(self): def test_submitting_other_contacts_deletes_no_other_contacts_rationale(self):
"""When a user submits the Other Contacts form with other contacts selected, the domain request's """When a user submits the Other Contacts form with other contacts selected, the domain request's
no other contacts rationale gets deleted""" no other contacts rationale gets deleted"""
@ -1528,6 +1554,7 @@ class DomainRequestTests(TestWithUser, WebTest):
None, None,
) )
@less_console_noise_decorator
def test_submitting_no_other_contacts_rationale_deletes_other_contacts(self): def test_submitting_no_other_contacts_rationale_deletes_other_contacts(self):
"""When a user submits the Other Contacts form with no other contacts selected, the domain request's """When a user submits the Other Contacts form with no other contacts selected, the domain request's
other contacts get deleted for other contacts that exist and are not joined to other objects other contacts get deleted for other contacts that exist and are not joined to other objects
@ -1570,6 +1597,7 @@ class DomainRequestTests(TestWithUser, WebTest):
"Hello again!", "Hello again!",
) )
@less_console_noise_decorator
def test_submitting_no_other_contacts_rationale_removes_reference_other_contacts_when_joined(self): def test_submitting_no_other_contacts_rationale_removes_reference_other_contacts_when_joined(self):
"""When a user submits the Other Contacts form with no other contacts selected, the domain request's """When a user submits the Other Contacts form with no other contacts selected, the domain request's
other contacts references get removed for other contacts that exist and are joined to other objects""" other contacts references get removed for other contacts that exist and are joined to other objects"""
@ -1665,6 +1693,7 @@ class DomainRequestTests(TestWithUser, WebTest):
"Hello again!", "Hello again!",
) )
@less_console_noise_decorator
def test_if_yes_no_form_is_no_then_no_other_contacts_required(self): def test_if_yes_no_form_is_no_then_no_other_contacts_required(self):
"""Applicants with no other contacts have to give a reason.""" """Applicants with no other contacts have to give a reason."""
other_contacts_page = self.app.get(reverse("domain-request:other_contacts")) other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
@ -1680,6 +1709,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# Assert that it is not returned, ie the contacts form is not required # Assert that it is not returned, ie the contacts form is not required
self.assertNotContains(response, "Enter the first name / given name of this contact.") self.assertNotContains(response, "Enter the first name / given name of this contact.")
@less_console_noise_decorator
def test_if_yes_no_form_is_yes_then_other_contacts_required(self): def test_if_yes_no_form_is_yes_then_other_contacts_required(self):
"""Applicants with other contacts do not have to give a reason.""" """Applicants with other contacts do not have to give a reason."""
other_contacts_page = self.app.get(reverse("domain-request:other_contacts")) other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
@ -1695,6 +1725,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# Assert that it is returned, ie the contacts form is required # Assert that it is returned, ie the contacts form is required
self.assertContains(response, "Enter the first name / given name of this contact.") self.assertContains(response, "Enter the first name / given name of this contact.")
@less_console_noise_decorator
def test_delete_other_contact(self): def test_delete_other_contact(self):
"""Other contacts can be deleted after being saved to database. """Other contacts can be deleted after being saved to database.
@ -1779,6 +1810,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(domain_request.other_contacts.count(), 1) self.assertEqual(domain_request.other_contacts.count(), 1)
self.assertEqual(domain_request.other_contacts.first().first_name, "Testy3") self.assertEqual(domain_request.other_contacts.first().first_name, "Testy3")
@less_console_noise_decorator
def test_delete_other_contact_does_not_allow_zero_contacts(self): def test_delete_other_contact_does_not_allow_zero_contacts(self):
"""Delete Other Contact does not allow submission with zero contacts.""" """Delete Other Contact does not allow submission with zero contacts."""
# Populate the database with a domain request that # Populate the database with a domain request that
@ -1851,6 +1883,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(domain_request.other_contacts.count(), 1) self.assertEqual(domain_request.other_contacts.count(), 1)
self.assertEqual(domain_request.other_contacts.first().first_name, "Testy2") self.assertEqual(domain_request.other_contacts.first().first_name, "Testy2")
@less_console_noise_decorator
def test_delete_other_contact_sets_visible_empty_form_as_required_after_failed_submit(self): def test_delete_other_contact_sets_visible_empty_form_as_required_after_failed_submit(self):
"""When you: """When you:
1. add an empty contact, 1. add an empty contact,
@ -1928,6 +1961,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# Enter the first name ... # Enter the first name ...
self.assertContains(response, "Enter the first name / given name of this contact.") self.assertContains(response, "Enter the first name / given name of this contact.")
@less_console_noise_decorator
def test_edit_other_contact_in_place(self): def test_edit_other_contact_in_place(self):
"""When you: """When you:
1. edit an existing contact which is not joined to another model, 1. edit an existing contact which is not joined to another model,
@ -2009,6 +2043,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEquals(other_contact_pk, other_contact.id) self.assertEquals(other_contact_pk, other_contact.id)
self.assertEquals("Testy3", other_contact.first_name) self.assertEquals("Testy3", other_contact.first_name)
@less_console_noise_decorator
def test_edit_other_contact_creates_new(self): def test_edit_other_contact_creates_new(self):
"""When you: """When you:
1. edit an existing contact which IS joined to another model, 1. edit an existing contact which IS joined to another model,
@ -2089,6 +2124,7 @@ class DomainRequestTests(TestWithUser, WebTest):
senior_official = domain_request.senior_official senior_official = domain_request.senior_official
self.assertEquals("Testy", senior_official.first_name) self.assertEquals("Testy", senior_official.first_name)
@less_console_noise_decorator
def test_edit_senior_official_in_place(self): def test_edit_senior_official_in_place(self):
"""When you: """When you:
1. edit a senior official which is not joined to another model, 1. edit a senior official which is not joined to another model,
@ -2154,6 +2190,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEquals(so_pk, updated_so.id) self.assertEquals(so_pk, updated_so.id)
self.assertEquals("Testy2", updated_so.first_name) self.assertEquals("Testy2", updated_so.first_name)
@less_console_noise_decorator
def test_edit_senior_official_creates_new(self): def test_edit_senior_official_creates_new(self):
"""When you: """When you:
1. edit an existing senior official which IS joined to another model, 1. edit an existing senior official which IS joined to another model,
@ -2226,6 +2263,7 @@ class DomainRequestTests(TestWithUser, WebTest):
senior_official = domain_request.senior_official senior_official = domain_request.senior_official
self.assertEquals("Testy2", senior_official.first_name) self.assertEquals("Testy2", senior_official.first_name)
@less_console_noise_decorator
def test_edit_submitter_in_place(self): def test_edit_submitter_in_place(self):
"""When you: """When you:
1. edit a submitter (your contact) which is not joined to another model, 1. edit a submitter (your contact) which is not joined to another model,
@ -2290,6 +2328,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEquals(submitter_pk, updated_submitter.id) self.assertEquals(submitter_pk, updated_submitter.id)
self.assertEquals("Testy2", updated_submitter.first_name) self.assertEquals("Testy2", updated_submitter.first_name)
@less_console_noise_decorator
def test_edit_submitter_creates_new(self): def test_edit_submitter_creates_new(self):
"""When you: """When you:
1. edit an existing your contact which IS joined to another model, 1. edit an existing your contact which IS joined to another model,
@ -2362,6 +2401,7 @@ class DomainRequestTests(TestWithUser, WebTest):
submitter = domain_request.submitter submitter = domain_request.submitter
self.assertEquals("Testy2", submitter.first_name) self.assertEquals("Testy2", submitter.first_name)
@less_console_noise_decorator
def test_domain_request_about_your_organiztion_interstate(self): def test_domain_request_about_your_organiztion_interstate(self):
"""Special districts have to answer an additional question.""" """Special districts have to answer an additional question."""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -2390,6 +2430,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION])
@less_console_noise_decorator
def test_domain_request_tribal_government(self): def test_domain_request_tribal_government(self):
"""Tribal organizations have to answer an additional question.""" """Tribal organizations have to answer an additional question."""
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
@ -2421,6 +2462,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# and the step is on the sidebar list. # and the step is on the sidebar list.
self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT]) self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT])
@less_console_noise_decorator
def test_domain_request_so_dynamic_text(self): def test_domain_request_so_dynamic_text(self):
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
@ -2460,9 +2502,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_page = federal_result.follow() org_contact_page = federal_result.follow()
org_contact_form = org_contact_page.forms[0] org_contact_form = org_contact_page.forms[0]
# federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = self.federal_agency.id
federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
org_contact_form["organization_contact-federal_agency"] = federal_agency.id
org_contact_form["organization_contact-organization_name"] = "Testorg" org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1" org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-address_line2"] = "address 2" org_contact_form["organization_contact-address_line2"] = "address 2"
@ -2493,6 +2533,7 @@ class DomainRequestTests(TestWithUser, WebTest):
so_page = election_page.click(str(self.TITLES["senior_official"]), index=0) so_page = election_page.click(str(self.TITLES["senior_official"]), index=0)
self.assertContains(so_page, "Domain requests from cities") self.assertContains(so_page, "Domain requests from cities")
@less_console_noise_decorator
def test_domain_request_dotgov_domain_dynamic_text(self): def test_domain_request_dotgov_domain_dynamic_text(self):
intro_page = self.app.get(reverse("domain-request:")) intro_page = self.app.get(reverse("domain-request:"))
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
@ -2532,9 +2573,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_page = federal_result.follow() org_contact_page = federal_result.follow()
org_contact_form = org_contact_page.forms[0] org_contact_form = org_contact_page.forms[0]
# federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = self.federal_agency.id
federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
org_contact_form["organization_contact-federal_agency"] = federal_agency.id
org_contact_form["organization_contact-organization_name"] = "Testorg" org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1" org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-address_line2"] = "address 2" org_contact_form["organization_contact-address_line2"] = "address 2"
@ -2595,6 +2634,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(dotgov_page, "CityofEudoraKS.gov") self.assertContains(dotgov_page, "CityofEudoraKS.gov")
self.assertNotContains(dotgov_page, "medicare.gov") self.assertNotContains(dotgov_page, "medicare.gov")
@less_console_noise_decorator
def test_domain_request_formsets(self): def test_domain_request_formsets(self):
"""Users are able to add more than one of some fields.""" """Users are able to add more than one of some fields."""
current_sites_page = self.app.get(reverse("domain-request:current_sites")) current_sites_page = self.app.get(reverse("domain-request:current_sites"))
@ -2749,6 +2789,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# page = self.app.get(url) # page = self.app.get(url)
# self.assertNotContains(page, "VALUE") # self.assertNotContains(page, "VALUE")
@less_console_noise_decorator
def test_long_org_name_in_domain_request(self): def test_long_org_name_in_domain_request(self):
""" """
Make sure the long name is displaying in the domain request form, Make sure the long name is displaying in the domain request form,
@ -2771,6 +2812,7 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(type_page, "Federal: an agency of the U.S. government") self.assertContains(type_page, "Federal: an agency of the U.S. government")
@less_console_noise_decorator
def test_submit_modal_no_domain_text_fallback(self): def test_submit_modal_no_domain_text_fallback(self):
"""When user clicks on submit your domain request and the requested domain """When user clicks on submit your domain request and the requested domain
is null (possible through url direct access to the review page), present is null (possible through url direct access to the review page), present
@ -2790,6 +2832,12 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
self.app.set_user(self.user.username) self.app.set_user(self.user.username)
self.client.force_login(self.user) self.client.force_login(self.user)
def tearDown(self):
super().tearDown()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
@less_console_noise_decorator
def test_domain_request_status(self): def test_domain_request_status(self):
"""Checking domain request status page""" """Checking domain request status page"""
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
@ -2803,6 +2851,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
self.assertContains(detail_page, "Admin Tester") self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:") self.assertContains(detail_page, "Status:")
@less_console_noise_decorator
def test_domain_request_status_with_ineligible_user(self): def test_domain_request_status_with_ineligible_user(self):
"""Checking domain request status page whith a blocked user. """Checking domain request status page whith a blocked user.
The user should still have access to view.""" The user should still have access to view."""
@ -2819,6 +2868,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
self.assertContains(detail_page, "Admin Tester") self.assertContains(detail_page, "Admin Tester")
self.assertContains(detail_page, "Status:") self.assertContains(detail_page, "Status:")
@less_console_noise_decorator
def test_domain_request_withdraw(self): def test_domain_request_withdraw(self):
"""Checking domain request status page""" """Checking domain request status page"""
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
@ -2849,6 +2899,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
response = self.client.get("/get-domain-requests-json/") response = self.client.get("/get-domain-requests-json/")
self.assertContains(response, "Withdrawn") self.assertContains(response, "Withdrawn")
@less_console_noise_decorator
def test_domain_request_withdraw_no_permissions(self): def test_domain_request_withdraw_no_permissions(self):
"""Can't withdraw domain requests as a restricted user.""" """Can't withdraw domain requests as a restricted user."""
self.user.status = User.RESTRICTED self.user.status = User.RESTRICTED
@ -2873,6 +2924,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
page = self.client.get(reverse(url_name, kwargs={"pk": domain_request.pk})) page = self.client.get(reverse(url_name, kwargs={"pk": domain_request.pk}))
self.assertEqual(page.status_code, 403) self.assertEqual(page.status_code, 403)
@less_console_noise_decorator
def test_domain_request_status_no_permissions(self): def test_domain_request_status_no_permissions(self):
"""Can't access domain requests without being the creator.""" """Can't access domain requests without being the creator."""
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user) domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
@ -2892,6 +2944,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
page = self.client.get(reverse(url_name, kwargs={"pk": domain_request.pk})) page = self.client.get(reverse(url_name, kwargs={"pk": domain_request.pk}))
self.assertEqual(page.status_code, 403) self.assertEqual(page.status_code, 403)
@less_console_noise_decorator
def test_approved_domain_request_not_in_active_requests(self): def test_approved_domain_request_not_in_active_requests(self):
"""An approved domain request is not shown in the Active """An approved domain request is not shown in the Active
Requests table on home.html.""" Requests table on home.html."""
@ -2916,13 +2969,17 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
def tearDown(self): def tearDown(self):
super().tearDown() super().tearDown()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
@less_console_noise_decorator
def test_unlocked_steps_empty_domain_request(self): def test_unlocked_steps_empty_domain_request(self):
"""Test when all fields in the domain request are empty.""" """Test when all fields in the domain request are empty."""
unlocked_steps = self.wizard.db_check_for_unlocking_steps() unlocked_steps = self.wizard.db_check_for_unlocking_steps()
expected_dict = [] expected_dict = []
self.assertEqual(unlocked_steps, expected_dict) self.assertEqual(unlocked_steps, expected_dict)
@less_console_noise_decorator
def test_unlocked_steps_full_domain_request(self): def test_unlocked_steps_full_domain_request(self):
"""Test when all fields in the domain request are filled.""" """Test when all fields in the domain request are filled."""
@ -2959,6 +3016,7 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
else: else:
self.fail(f"Expected a redirect, but got a different response: {response}") self.fail(f"Expected a redirect, but got a different response: {response}")
@less_console_noise_decorator
def test_unlocked_steps_partial_domain_request(self): def test_unlocked_steps_partial_domain_request(self):
"""Test when some fields in the domain request are filled.""" """Test when some fields in the domain request are filled."""

View file

@ -12,99 +12,102 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
super().setUp() super().setUp()
self.app.set_user(self.user.username) self.app.set_user(self.user.username)
@classmethod
def setUpClass(cls):
super().setUpClass()
lamb_chops, _ = DraftDomain.objects.get_or_create(name="lamb-chops.gov") lamb_chops, _ = DraftDomain.objects.get_or_create(name="lamb-chops.gov")
short_ribs, _ = DraftDomain.objects.get_or_create(name="short-ribs.gov") short_ribs, _ = DraftDomain.objects.get_or_create(name="short-ribs.gov")
beef_chuck, _ = DraftDomain.objects.get_or_create(name="beef-chuck.gov") beef_chuck, _ = DraftDomain.objects.get_or_create(name="beef-chuck.gov")
stew_beef, _ = DraftDomain.objects.get_or_create(name="stew-beef.gov") stew_beef, _ = DraftDomain.objects.get_or_create(name="stew-beef.gov")
# Create domain requests for the user # Create domain requests for the user
self.domain_requests = [ cls.domain_requests = [
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=lamb_chops, requested_domain=lamb_chops,
submission_date="2024-01-01", submission_date="2024-01-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-01-01", created_at="2024-01-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=short_ribs, requested_domain=short_ribs,
submission_date="2024-02-01", submission_date="2024-02-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-02-01", created_at="2024-02-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=beef_chuck, requested_domain=beef_chuck,
submission_date="2024-03-01", submission_date="2024-03-01",
status=DomainRequest.DomainRequestStatus.REJECTED, status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-03-01", created_at="2024-03-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=stew_beef, requested_domain=stew_beef,
submission_date="2024-04-01", submission_date="2024-04-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-04-01", created_at="2024-04-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-05-01", submission_date="2024-05-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-05-01", created_at="2024-05-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-06-01", submission_date="2024-06-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-06-01", created_at="2024-06-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-07-01", submission_date="2024-07-01",
status=DomainRequest.DomainRequestStatus.REJECTED, status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-07-01", created_at="2024-07-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-08-01", submission_date="2024-08-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-08-01", created_at="2024-08-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-09-01", submission_date="2024-09-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-09-01", created_at="2024-09-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-10-01", submission_date="2024-10-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-10-01", created_at="2024-10-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-11-01", submission_date="2024-11-01",
status=DomainRequest.DomainRequestStatus.REJECTED, status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-11-01", created_at="2024-11-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-11-02", submission_date="2024-11-02",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-11-02", created_at="2024-11-02",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=self.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-12-01", submission_date="2024-12-01",
status=DomainRequest.DomainRequestStatus.APPROVED, status=DomainRequest.DomainRequestStatus.APPROVED,
@ -112,9 +115,11 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
), ),
] ]
def tearDown(self): @classmethod
super().tearDown() def tearDownClass(cls):
super().tearDownClass()
DomainRequest.objects.all().delete() DomainRequest.objects.all().delete()
DraftDomain.objects.all().delete()
def test_get_domain_requests_json_authenticated(self): def test_get_domain_requests_json_authenticated(self):
"""Test that domain requests are returned properly for an authenticated user.""" """Test that domain requests are returned properly for an authenticated user."""

View file

@ -109,7 +109,7 @@ class BaseExport(ABC):
return Q() return Q()
@classmethod @classmethod
def get_filter_conditions(cls, start_date=None, end_date=None): def get_filter_conditions(cls, **export_kwargs):
""" """
Get a Q object of filter conditions to filter when building queryset. Get a Q object of filter conditions to filter when building queryset.
""" """
@ -145,7 +145,7 @@ class BaseExport(ABC):
return queryset return queryset
@classmethod @classmethod
def write_csv_before(cls, csv_writer, start_date=None, end_date=None): def write_csv_before(cls, csv_writer, **export_kwargs):
""" """
Write to csv file before the write_csv method. Write to csv file before the write_csv method.
Override in subclasses where needed. Override in subclasses where needed.
@ -192,7 +192,7 @@ class BaseExport(ABC):
return cls.update_queryset(queryset, **kwargs) return cls.update_queryset(queryset, **kwargs)
@classmethod @classmethod
def export_data_to_csv(cls, csv_file, start_date=None, end_date=None): def export_data_to_csv(cls, csv_file, **export_kwargs):
""" """
All domain metadata: All domain metadata:
Exports domains of all statuses plus domain managers. Exports domains of all statuses plus domain managers.
@ -205,7 +205,7 @@ class BaseExport(ABC):
prefetch_related = cls.get_prefetch_related() prefetch_related = cls.get_prefetch_related()
exclusions = cls.get_exclusions() exclusions = cls.get_exclusions()
annotations_for_sort = cls.get_annotations_for_sort() annotations_for_sort = cls.get_annotations_for_sort()
filter_conditions = cls.get_filter_conditions(start_date, end_date) filter_conditions = cls.get_filter_conditions(**export_kwargs)
computed_fields = cls.get_computed_fields() computed_fields = cls.get_computed_fields()
related_table_fields = cls.get_related_table_fields() related_table_fields = cls.get_related_table_fields()
@ -227,10 +227,13 @@ class BaseExport(ABC):
models_dict = convert_queryset_to_dict(annotated_queryset, is_model=False) models_dict = convert_queryset_to_dict(annotated_queryset, is_model=False)
# Write to csv file before the write_csv # Write to csv file before the write_csv
cls.write_csv_before(writer, start_date, end_date) cls.write_csv_before(writer, **export_kwargs)
# Write the csv file # Write the csv file
cls.write_csv(writer, columns, models_dict) rows = cls.write_csv(writer, columns, models_dict)
# Return rows that for easier parsing and testing
return rows
@classmethod @classmethod
def write_csv( def write_csv(
@ -257,6 +260,9 @@ class BaseExport(ABC):
writer.writerows(rows) writer.writerows(rows)
# Return rows for easier parsing and testing
return rows
@classmethod @classmethod
@abstractmethod @abstractmethod
def parse_row(cls, columns, model): def parse_row(cls, columns, model):
@ -344,7 +350,11 @@ class DomainExport(BaseExport):
""" """
Fetch all UserDomainRole entries and return a mapping of domain to user__email. Fetch all UserDomainRole entries and return a mapping of domain to user__email.
""" """
user_domain_roles = UserDomainRole.objects.select_related("user").values_list("domain__name", "user__email") user_domain_roles = (
UserDomainRole.objects.select_related("user")
.order_by("domain__name", "user__email")
.values_list("domain__name", "user__email")
)
return list(user_domain_roles) return list(user_domain_roles)
@classmethod @classmethod
@ -554,6 +564,25 @@ class DomainDataType(DomainExport):
] ]
class DomainDataTypeUser(DomainDataType):
"""
The DomainDataType report, but sliced on the current request user
"""
@classmethod
def get_filter_conditions(cls, request=None):
"""
Get a Q object of filter conditions to filter when building queryset.
"""
if request is None or not hasattr(request, "user") or not request.user:
# Return nothing
return Q(id__in=[])
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
return Q(domain__id__in=domain_ids)
class DomainDataFull(DomainExport): class DomainDataFull(DomainExport):
""" """
Shows security contacts, filtered by state Shows security contacts, filtered by state
@ -611,7 +640,7 @@ class DomainDataFull(DomainExport):
return ["domain"] return ["domain"]
@classmethod @classmethod
def get_filter_conditions(cls, start_date=None, end_date=None): def get_filter_conditions(cls):
""" """
Get a Q object of filter conditions to filter when building queryset. Get a Q object of filter conditions to filter when building queryset.
""" """
@ -706,7 +735,7 @@ class DomainDataFederal(DomainExport):
return ["domain"] return ["domain"]
@classmethod @classmethod
def get_filter_conditions(cls, start_date=None, end_date=None): def get_filter_conditions(cls):
""" """
Get a Q object of filter conditions to filter when building queryset. Get a Q object of filter conditions to filter when building queryset.
""" """

View file

@ -1,4 +1,8 @@
import logging
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.contrib import messages
from registrar.forms.portfolio import PortfolioOrgAddressForm
from registrar.models.portfolio import Portfolio from registrar.models.portfolio import Portfolio
from registrar.views.utility.permission_views import ( from registrar.views.utility.permission_views import (
PortfolioDomainRequestsPermissionView, PortfolioDomainRequestsPermissionView,
@ -7,6 +11,10 @@ from registrar.views.utility.permission_views import (
) )
from waffle.decorators import flag_is_active from waffle.decorators import flag_is_active
from django.views.generic import View from django.views.generic import View
from django.views.generic.edit import FormMixin
logger = logging.getLogger(__name__)
class PortfolioDomainsView(PortfolioDomainsPermissionView, View): class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
@ -42,17 +50,61 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
return render(request, "portfolio_requests.html", context) return render(request, "portfolio_requests.html", context)
class PortfolioOrganizationView(PortfolioBasePermissionView, View): class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
"""
View to handle displaying and updating the portfolio's organization details.
"""
model = Portfolio
template_name = "portfolio_organization.html" template_name = "portfolio_organization.html"
form_class = PortfolioOrgAddressForm
context_object_name = "portfolio"
def get(self, request, portfolio_id): def get_context_data(self, **kwargs):
context = {} """Add additional context data to the template."""
context = super().get_context_data(**kwargs)
# no need to add portfolio to request context here
context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature")
context["has_organization_feature_flag"] = flag_is_active(self.request, "organization_feature")
return context
if self.request.user.is_authenticated: def get_object(self, queryset=None):
context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") """Get the portfolio object based on the URL parameter."""
context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature") return get_object_or_404(Portfolio, id=self.kwargs.get("portfolio_id"))
portfolio = get_object_or_404(Portfolio, id=portfolio_id)
context["portfolio"] = portfolio
return render(request, "portfolio_organization.html", context) def get_form_kwargs(self):
"""Include the instance in the form kwargs."""
kwargs = super().get_form_kwargs()
kwargs["instance"] = self.get_object()
return kwargs
def get(self, request, *args, **kwargs):
"""Handle GET requests to display the form."""
self.object = self.get_object()
form = self.get_form()
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
"""Handle POST requests to process form submission."""
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
"""Handle the case when the form is valid."""
self.object = form.save(commit=False)
self.object.creator = self.request.user
self.object.save()
messages.success(self.request, "The organization information for this portfolio has been updated.")
return super().form_valid(form)
def form_invalid(self, form):
"""Handle the case when the form is invalid."""
return self.render_to_response(self.get_context_data(form=form))
def get_success_url(self):
"""Redirect to the overview page for the portfolio."""
return reverse("portfolio-organization", kwargs={"portfolio_id": self.object.pk})

View file

@ -158,6 +158,17 @@ class ExportDataType(View):
return response return response
class ExportDataTypeUser(View):
"""Returns a domain report for a given user on the request"""
def get(self, request, *args, **kwargs):
# match the CSV example with all the fields
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="your-domains.csv"'
csv_export.DomainDataTypeUser.export_data_to_csv(response, request=request)
return response
class ExportDataFull(View): class ExportDataFull(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# Smaller export based on 1 # Smaller export based on 1
@ -194,7 +205,7 @@ class ExportDataDomainsGrowth(View):
response = HttpResponse(content_type="text/csv") response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="domain-growth-report-{start_date}-to-{end_date}.csv"' response["Content-Disposition"] = f'attachment; filename="domain-growth-report-{start_date}-to-{end_date}.csv"'
csv_export.DomainGrowth.export_data_to_csv(response, start_date, end_date) csv_export.DomainGrowth.export_data_to_csv(response, start_date=start_date, end_date=end_date)
return response return response
@ -206,7 +217,7 @@ class ExportDataRequestsGrowth(View):
response = HttpResponse(content_type="text/csv") response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="requests-{start_date}-to-{end_date}.csv"' response["Content-Disposition"] = f'attachment; filename="requests-{start_date}-to-{end_date}.csv"'
csv_export.DomainRequestGrowth.export_data_to_csv(response, start_date, end_date) csv_export.DomainRequestGrowth.export_data_to_csv(response, start_date=start_date, end_date=end_date)
return response return response
@ -217,7 +228,7 @@ class ExportDataManagedDomains(View):
end_date = request.GET.get("end_date", "") end_date = request.GET.get("end_date", "")
response = HttpResponse(content_type="text/csv") response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="managed-domains-{start_date}-to-{end_date}.csv"' response["Content-Disposition"] = f'attachment; filename="managed-domains-{start_date}-to-{end_date}.csv"'
csv_export.DomainManaged.export_data_to_csv(response, start_date, end_date) csv_export.DomainManaged.export_data_to_csv(response, start_date=start_date, end_date=end_date)
return response return response
@ -228,6 +239,6 @@ class ExportDataUnmanagedDomains(View):
end_date = request.GET.get("end_date", "") end_date = request.GET.get("end_date", "")
response = HttpResponse(content_type="text/csv") response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="unmanaged-domains-{start_date}-to-{end_date}.csv"' response["Content-Disposition"] = f'attachment; filename="unmanaged-domains-{start_date}-to-{end_date}.csv"'
csv_export.DomainUnmanaged.export_data_to_csv(response, start_date, end_date) csv_export.DomainUnmanaged.export_data_to_csv(response, start_date=start_date, end_date=end_date)
return response return response