diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f65007b2b..dec0b9fac 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -48,7 +48,7 @@ All other changes require just a single approving review.--> - [ ] Added at least 2 developers as PR reviewers (only 1 will need to approve) - [ ] Messaged on Slack or in standup to notify the team that a PR is ready for review - [ ] Changes to “how we do things” are documented in READMEs and or onboarding guide -- [ ] If any model was updated to modify/add/delete columns, makemigrations was ran and the assoicated migrations file has been commited. +- [ ] If any model was updated to modify/add/delete columns, makemigrations was ran and the associated migrations file has been commited. #### Ensured code standards are met (Original Developer) @@ -72,7 +72,7 @@ All other changes require just a single approving review.--> - [ ] Reviewed this code and left comments - [ ] Checked that all code is adequately covered by tests - [ ] Made it clear which comments need to be addressed before this work is merged -- [ ] If any model was updated to modify/add/delete columns, makemigrations was ran and the assoicated migrations file has been commited. +- [ ] If any model was updated to modify/add/delete columns, makemigrations was ran and the associated migrations file has been commited. #### Ensured code standards are met (Code reviewer) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6d34f80b5..fd9e31b91 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -36,7 +36,7 @@ jobs: - name: Unit tests working-directory: ./src - run: docker compose run app python manage.py test + run: docker compose run app python manage.py test --parallel django-migrations-complete: runs-on: ubuntu-latest diff --git a/docs/developer/README.md b/docs/developer/README.md index 57985d6e2..dc4c9ddd2 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -145,7 +145,7 @@ You can change the logging verbosity, if needed. Do a web search for "django log ## Mock data -There is a `post_migrate` signal in [signals.py](../../src/registrar/signals.py) that will load the fixtures from [fixtures_user.py](../../src/registrar/fixtures_users.py) and [fixtures_applications.py](../../src/registrar/fixtures_applications.py), giving you some test data to play with while developing. +[load.py](../../src/registrar/management/commands/load.py) called from docker-compose (locally) and reset-db.yml (upper) loads the fixtures from [fixtures_user.py](../../src/registrar/fixtures_users.py) and [fixtures_applications.py](../../src/registrar/fixtures_applications.py), giving you some test data to play with while developing. See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures. diff --git a/src/djangooidc/tests/test_views.py b/src/djangooidc/tests/test_views.py index 63b23df96..4193f723b 100644 --- a/src/djangooidc/tests/test_views.py +++ b/src/djangooidc/tests/test_views.py @@ -35,155 +35,155 @@ class ViewsTest(TestCase): pass def test_openid_sets_next(self, mock_client): - # setup - callback_url = reverse("openid_login_callback") - # mock - mock_client.create_authn_request.side_effect = self.say_hi - mock_client.get_default_acr_value.side_effect = self.create_acr - # test - response = self.client.get(reverse("login"), {"next": callback_url}) - # assert - session = mock_client.create_authn_request.call_args[0][0] - self.assertEqual(session["next"], callback_url) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Hi") + with less_console_noise(): + # setup + callback_url = reverse("openid_login_callback") + # mock + mock_client.create_authn_request.side_effect = self.say_hi + mock_client.get_default_acr_value.side_effect = self.create_acr + # test + response = self.client.get(reverse("login"), {"next": callback_url}) + # assert + session = mock_client.create_authn_request.call_args[0][0] + self.assertEqual(session["next"], callback_url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Hi") def test_openid_raises(self, mock_client): - # mock - mock_client.create_authn_request.side_effect = Exception("Test") - # test with less_console_noise(): + # mock + mock_client.create_authn_request.side_effect = Exception("Test") + # test response = self.client.get(reverse("login")) - # assert - self.assertEqual(response.status_code, 500) - self.assertTemplateUsed(response, "500.html") - self.assertIn("Server error", response.content.decode("utf-8")) + # assert + self.assertEqual(response.status_code, 500) + self.assertTemplateUsed(response, "500.html") + self.assertIn("Server error", response.content.decode("utf-8")) def test_callback_with_no_session_state(self, mock_client): """If the local session is None (ie the server restarted while user was logged out), we do not throw an exception. Rather, we attempt to login again.""" - # mock - mock_client.get_default_acr_value.side_effect = self.create_acr - mock_client.callback.side_effect = NoStateDefined() - # test with less_console_noise(): + # mock + mock_client.get_default_acr_value.side_effect = self.create_acr + mock_client.callback.side_effect = NoStateDefined() + # test response = self.client.get(reverse("openid_login_callback")) - # assert - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, "/") + # assert + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/") def test_login_callback_reads_next(self, mock_client): - # setup - session = self.client.session - session["next"] = reverse("logout") - session.save() - # mock - mock_client.callback.side_effect = self.user_info - # test - with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): - response = self.client.get(reverse("openid_login_callback")) - # assert - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse("logout")) + with less_console_noise(): + # setup + session = self.client.session + session["next"] = reverse("logout") + session.save() + # mock + mock_client.callback.side_effect = self.user_info + # test + with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): + response = self.client.get(reverse("openid_login_callback")) + # assert + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, reverse("logout")) def test_login_callback_no_step_up_auth(self, mock_client): """Walk through login_callback when requires_step_up_auth returns False and assert that we have a redirect to /""" - # setup - session = self.client.session - session.save() - # mock - mock_client.callback.side_effect = self.user_info - # test - with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): - response = self.client.get(reverse("openid_login_callback")) - # assert - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, "/") + with less_console_noise(): + # setup + session = self.client.session + session.save() + # mock + mock_client.callback.side_effect = self.user_info + # test + with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): + response = self.client.get(reverse("openid_login_callback")) + # assert + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/") def test_requires_step_up_auth(self, mock_client): """Invoke login_callback passing it a request when requires_step_up_auth returns True and assert that session is updated and create_authn_request (mock) is called.""" - # Configure the mock to return an expected value for get_step_up_acr_value - mock_client.return_value.get_step_up_acr_value.return_value = "step_up_acr_value" - - # Create a mock request - request = self.factory.get("/some-url") - request.session = {"acr_value": ""} - - # Ensure that the CLIENT instance used in login_callback is the mock - # patch requires_step_up_auth to return True - with patch("djangooidc.views.requires_step_up_auth", return_value=True), patch( - "djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock() - ) as mock_create_authn_request: - login_callback(request) - - # create_authn_request only gets called when requires_step_up_auth is True - # and it changes this acr_value in request.session - - # Assert that acr_value is no longer empty string - self.assertNotEqual(request.session["acr_value"], "") - # And create_authn_request was called again - mock_create_authn_request.assert_called_once() + with less_console_noise(): + # Configure the mock to return an expected value for get_step_up_acr_value + mock_client.return_value.get_step_up_acr_value.return_value = "step_up_acr_value" + # Create a mock request + request = self.factory.get("/some-url") + request.session = {"acr_value": ""} + # Ensure that the CLIENT instance used in login_callback is the mock + # patch requires_step_up_auth to return True + with patch("djangooidc.views.requires_step_up_auth", return_value=True), patch( + "djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock() + ) as mock_create_authn_request: + login_callback(request) + # create_authn_request only gets called when requires_step_up_auth is True + # and it changes this acr_value in request.session + # Assert that acr_value is no longer empty string + self.assertNotEqual(request.session["acr_value"], "") + # And create_authn_request was called again + mock_create_authn_request.assert_called_once() def test_does_not_requires_step_up_auth(self, mock_client): """Invoke login_callback passing it a request when requires_step_up_auth returns False and assert that session is not updated and create_authn_request (mock) is not called. Possibly redundant with test_login_callback_requires_step_up_auth""" - # Create a mock request - request = self.factory.get("/some-url") - request.session = {"acr_value": ""} - - # Ensure that the CLIENT instance used in login_callback is the mock - # patch requires_step_up_auth to return False - with patch("djangooidc.views.requires_step_up_auth", return_value=False), patch( - "djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock() - ) as mock_create_authn_request: - login_callback(request) - - # create_authn_request only gets called when requires_step_up_auth is True - # and it changes this acr_value in request.session - - # Assert that acr_value is NOT updated by testing that it is still an empty string - self.assertEqual(request.session["acr_value"], "") - # Assert create_authn_request was not called - mock_create_authn_request.assert_not_called() + with less_console_noise(): + # Create a mock request + request = self.factory.get("/some-url") + request.session = {"acr_value": ""} + # Ensure that the CLIENT instance used in login_callback is the mock + # patch requires_step_up_auth to return False + with patch("djangooidc.views.requires_step_up_auth", return_value=False), patch( + "djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock() + ) as mock_create_authn_request: + login_callback(request) + # create_authn_request only gets called when requires_step_up_auth is True + # and it changes this acr_value in request.session + # Assert that acr_value is NOT updated by testing that it is still an empty string + self.assertEqual(request.session["acr_value"], "") + # Assert create_authn_request was not called + mock_create_authn_request.assert_not_called() @patch("djangooidc.views.authenticate") def test_login_callback_raises(self, mock_auth, mock_client): - # mock - mock_client.callback.side_effect = self.user_info - mock_auth.return_value = None - # test - with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): - response = self.client.get(reverse("openid_login_callback")) - # assert - self.assertEqual(response.status_code, 401) - self.assertTemplateUsed(response, "401.html") - self.assertIn("Unauthorized", response.content.decode("utf-8")) + with less_console_noise(): + # mock + mock_client.callback.side_effect = self.user_info + mock_auth.return_value = None + # test + with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise(): + response = self.client.get(reverse("openid_login_callback")) + # assert + self.assertEqual(response.status_code, 401) + self.assertTemplateUsed(response, "401.html") + self.assertIn("Unauthorized", response.content.decode("utf-8")) def test_logout_redirect_url(self, mock_client): - # setup - session = self.client.session - session["state"] = "TEST" # nosec B105 - session.save() - # mock - mock_client.callback.side_effect = self.user_info - mock_client.registration_response = {"post_logout_redirect_uris": ["http://example.com/back"]} - mock_client.provider_info = {"end_session_endpoint": "http://example.com/log_me_out"} - mock_client.client_id = "TEST" - # test with less_console_noise(): - response = self.client.get(reverse("logout")) - # assert - expected = ( - "http://example.com/log_me_out?client_id=TEST&state" - "=TEST&post_logout_redirect_uri=http%3A%2F%2Fexample.com%2Fback" - ) - actual = response.url - self.assertEqual(response.status_code, 302) - self.assertEqual(actual, expected) + # setup + session = self.client.session + session["state"] = "TEST" # nosec B105 + session.save() + # mock + mock_client.callback.side_effect = self.user_info + mock_client.registration_response = {"post_logout_redirect_uris": ["http://example.com/back"]} + mock_client.provider_info = {"end_session_endpoint": "http://example.com/log_me_out"} + mock_client.client_id = "TEST" + # test + with less_console_noise(): + response = self.client.get(reverse("logout")) + # assert + expected = ( + "http://example.com/log_me_out?client_id=TEST&state" + "=TEST&post_logout_redirect_uri=http%3A%2F%2Fexample.com%2Fback" + ) + actual = response.url + self.assertEqual(response.status_code, 302) + self.assertEqual(actual, expected) @patch("djangooidc.views.auth_logout") def test_logout_always_logs_out(self, mock_logout, _): @@ -194,12 +194,13 @@ class ViewsTest(TestCase): self.assertTrue(mock_logout.called) def test_logout_callback_redirects(self, _): - # setup - session = self.client.session - session["next"] = reverse("logout") - session.save() - # test - response = self.client.get(reverse("openid_logout_callback")) - # assert - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse("logout")) + with less_console_noise(): + # setup + session = self.client.session + session["next"] = reverse("logout") + session.save() + # test + response = self.client.get(reverse("openid_logout_callback")) + # assert + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, reverse("logout")) diff --git a/src/docker-compose.yml b/src/docker-compose.yml index ba6530674..fdf069f56 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -25,6 +25,8 @@ services: - DJANGO_SECRET_KEY=really-long-random-string-BNPecI7+s8jMahQcGHZ3XQ5yUfRrSibdapVLIz0UemdktVPofDKcoy # Run Django in debug mode on local - DJANGO_DEBUG=True + # Set DJANGO_LOG_LEVEL in env + - DJANGO_LOG_LEVEL # Run Django without production flags - IS_PRODUCTION=False # Tell Django where it is being hosted diff --git a/src/epplibwrapper/tests/common.py b/src/epplibwrapper/tests/common.py new file mode 100644 index 000000000..122965ae8 --- /dev/null +++ b/src/epplibwrapper/tests/common.py @@ -0,0 +1,51 @@ +import os +import logging + +from contextlib import contextmanager + + +def get_handlers(): + """Obtain pointers to all StreamHandlers.""" + handlers = {} + + rootlogger = logging.getLogger() + for h in rootlogger.handlers: + if isinstance(h, logging.StreamHandler): + handlers[h.name] = h + + for logger in logging.Logger.manager.loggerDict.values(): + if not isinstance(logger, logging.PlaceHolder): + for h in logger.handlers: + if isinstance(h, logging.StreamHandler): + handlers[h.name] = h + + return handlers + + +@contextmanager +def less_console_noise(): + """ + Context manager to use in tests to silence console logging. + + This is helpful on tests which trigger console messages + (such as errors) which are normal and expected. + + It can easily be removed to debug a failing test. + """ + restore = {} + handlers = get_handlers() + devnull = open(os.devnull, "w") + + # redirect all the streams + for handler in handlers.values(): + prior = handler.setStream(devnull) + restore[handler.name] = prior + try: + # run the test + yield + finally: + # restore the streams + for handler in handlers.values(): + handler.setStream(restore[handler.name]) + # close the file we opened + devnull.close() diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 1c36d26da..f8e556445 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -9,7 +9,7 @@ from epplibwrapper.socket import Socket from epplibwrapper.utility.pool import EPPConnectionPool from registrar.models.domain import registry from contextlib import ExitStack - +from .common import less_console_noise import logging try: @@ -135,23 +135,26 @@ class TestConnectionPool(TestCase): stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - # Restart the connection pool - registry.start_connection_pool() - # Pool should be running, and be the right size - self.assertEqual(registry.pool_status.connection_success, True) - self.assertEqual(registry.pool_status.pool_running, True) + with less_console_noise(): + # Restart the connection pool + registry.start_connection_pool() + # Pool should be running, and be the right size + self.assertEqual(registry.pool_status.connection_success, True) + self.assertEqual(registry.pool_status.pool_running, True) - # Send a command - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # Send a command + result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # Should this ever fail, it either means that the schema has changed, - # or the pool is broken. - # If the schema has changed: Update the associated infoDomain.xml file - self.assertEqual(result.__dict__, expected_result) + # Should this ever fail, it either means that the schema has changed, + # or the pool is broken. + # If the schema has changed: Update the associated infoDomain.xml file + self.assertEqual(result.__dict__, expected_result) - # The number of open pools should match the number of requested ones. - # If it is 0, then they failed to open - self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + # The number of open pools should match the number of requested ones. + # If it is 0, then they failed to open + self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + # Kill the connection pool + registry.kill_pool() @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_pool_restarts_on_send(self): @@ -198,35 +201,43 @@ class TestConnectionPool(TestCase): xml = (location).read_bytes() return xml + def do_nothing(command): + pass + # Mock what happens inside the "with" with ExitStack() as stack: stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing)) stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - # Kill the connection pool - registry.kill_pool() + with less_console_noise(): + # Start the connection pool + registry.start_connection_pool() + # Kill the connection pool + registry.kill_pool() - self.assertEqual(registry.pool_status.connection_success, False) - self.assertEqual(registry.pool_status.pool_running, False) + self.assertEqual(registry.pool_status.pool_running, False) - # An exception should be raised as end user will be informed - # that they cannot connect to EPP - with self.assertRaises(RegistryError): - expected = "InfoDomain failed to execute due to a connection error." + # An exception should be raised as end user will be informed + # that they cannot connect to EPP + with self.assertRaises(RegistryError): + expected = "InfoDomain failed to execute due to a connection error." + result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + self.assertEqual(result, expected) + + # A subsequent command should be successful, as the pool restarts result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(result, expected) + # Should this ever fail, it either means that the schema has changed, + # or the pool is broken. + # If the schema has changed: Update the associated infoDomain.xml file + self.assertEqual(result.__dict__, expected_result) - # A subsequent command should be successful, as the pool restarts - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - # Should this ever fail, it either means that the schema has changed, - # or the pool is broken. - # If the schema has changed: Update the associated infoDomain.xml file - self.assertEqual(result.__dict__, expected_result) - - # The number of open pools should match the number of requested ones. - # If it is 0, then they failed to open - self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + # The number of open pools should match the number of requested ones. + # If it is 0, then they failed to open + self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + # Kill the connection pool + registry.kill_pool() @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_raises_connection_error(self): @@ -236,13 +247,16 @@ class TestConnectionPool(TestCase): with ExitStack() as stack: stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + with less_console_noise(): + # Start the connection pool + registry.start_connection_pool() - # Pool should be running - self.assertEqual(registry.pool_status.connection_success, True) - self.assertEqual(registry.pool_status.pool_running, True) + # Pool should be running + self.assertEqual(registry.pool_status.connection_success, True) + self.assertEqual(registry.pool_status.pool_running, True) - # Try to send a command out - should fail - with self.assertRaises(RegistryError): - expected = "InfoDomain failed to execute due to a connection error." - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(result, expected) + # Try to send a command out - should fail + with self.assertRaises(RegistryError): + expected = "InfoDomain failed to execute due to a connection error." + result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + self.assertEqual(result, expected) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 93edb2782..4f54e14ce 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -85,6 +85,21 @@ class EPPConnectionPool(ConnectionPool): logger.error(message, exc_info=True) raise PoolError(code=PoolErrorCodes.KEEP_ALIVE_FAILED) from err + def _keepalive_periodic(self): + """Overriding _keepalive_periodic from geventconnpool so that PoolErrors + are properly handled, as opposed to printing to stdout""" + delay = float(self.keepalive) / self.size + while 1: + try: + with self.get() as c: + self._keepalive(c) + except PoolError as err: + logger.error(err.message, exc_info=True) + except self.exc_classes: + # Nothing to do, the pool will generate a new connection later + pass + gevent.sleep(delay) + def _create_socket(self, client, login) -> Socket: """Creates and returns a socket instance""" socket = Socket(client, login) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 325081575..1bfda0b84 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -626,7 +626,7 @@ class DomainInformationAdmin(ListHeaderAdmin): search_help_text = "Search by domain." fieldsets = [ - (None, {"fields": ["creator", "domain_application"]}), + (None, {"fields": ["creator", "domain_application", "notes"]}), ( "Type of organization", { @@ -677,6 +677,7 @@ class DomainInformationAdmin(ListHeaderAdmin): "type_of_work", "more_organization_information", "domain", + "domain_application", "submitter", "no_other_contacts_rationale", "anything_else", @@ -793,7 +794,7 @@ class DomainApplicationAdmin(ListHeaderAdmin): # Detail view form = DomainApplicationAdminForm fieldsets = [ - (None, {"fields": ["status", "investigator", "creator", "approved_domain"]}), + (None, {"fields": ["status", "investigator", "creator", "approved_domain", "notes"]}), ( "Type of organization", { @@ -845,6 +846,7 @@ class DomainApplicationAdmin(ListHeaderAdmin): "creator", "about_your_organization", "requested_domain", + "approved_domain", "alternative_domains", "purpose", "submitter", @@ -1047,6 +1049,13 @@ class DomainAdmin(ListHeaderAdmin): "deleted", ] + fieldsets = ( + ( + None, + {"fields": ["name", "state", "expiration_date", "first_ready", "deleted"]}, + ), + ) + # this ordering effects the ordering of results # in autocomplete_fields for domain ordering = ["name"] diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index cdbbc83ee..866c7bd7d 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -283,19 +283,20 @@ function enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, elementPk, (function (){ // Get the current date in the format YYYY-MM-DD - var currentDate = new Date().toISOString().split('T')[0]; + let currentDate = new Date().toISOString().split('T')[0]; // Default the value of the start date input field to the current date let startDateInput =document.getElementById('start'); - startDateInput.value = currentDate; - + // Default the value of the end date input field to the current date let endDateInput =document.getElementById('end'); - endDateInput.value = currentDate; let exportGrowthReportButton = document.getElementById('exportLink'); if (exportGrowthReportButton) { + startDateInput.value = currentDate; + endDateInput.value = currentDate; + exportGrowthReportButton.addEventListener('click', function() { // Get the selected start and end dates let startDate = startDateInput.value; diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index de7ef6172..587b95305 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -130,7 +130,7 @@ function inlineToast(el, id, style, msg) { } } -function _checkDomainAvailability(el) { +function checkDomainAvailability(el) { const callback = (response) => { toggleInputValidity(el, (response && response.available), msg=response.message); announce(el.id, response.message); @@ -154,9 +154,6 @@ function _checkDomainAvailability(el) { fetchJSON(`available/?domain=${el.value}`, callback); } -/** Call the API to see if the domain is good. */ -const checkDomainAvailability = debounce(_checkDomainAvailability); - /** Hides the toast message and clears the aira live region. */ function clearDomainAvailability(el) { el.classList.remove('usa-input--success'); @@ -206,13 +203,33 @@ function handleInputValidation(e) { } /** On button click, handles running any associated validators. */ -function handleValidationClick(e) { +function validateFieldInput(e) { const attribute = e.target.getAttribute("validate-for") || ""; if (!attribute.length) return; const input = document.getElementById(attribute); + removeFormErrors(input, true); runValidators(input); } + +function validateFormsetInputs(e, availabilityButton) { + + // Collect input IDs from the repeatable forms + let inputs = Array.from(document.querySelectorAll('.repeatable-form input')) + + // Run validators for each input + inputs.forEach(input => { + removeFormErrors(input, true); + runValidators(input); + }); + + // Set the validate-for attribute on the button with the collected input IDs + // Not needed for functionality but nice for accessibility + inputs = inputs.map(input => input.id).join(', '); + availabilityButton.setAttribute('validate-for', inputs); + +} + // <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>> // Initialization code. @@ -232,14 +249,64 @@ function handleValidationClick(e) { for(const input of needsValidation) { input.addEventListener('input', handleInputValidation); } + const alternativeDomainsAvailability = document.getElementById('validate-alt-domains-availability'); const activatesValidation = document.querySelectorAll('[validate-for]'); + for(const button of activatesValidation) { - button.addEventListener('click', handleValidationClick); + // Adds multi-field validation for alternative domains + if (button === alternativeDomainsAvailability) { + button.addEventListener('click', (e) => { + validateFormsetInputs(e, alternativeDomainsAvailability) + }); + } else { + button.addEventListener('click', validateFieldInput); + } } })(); /** - * Delete method for formsets that diff in the view and delete in the model (Nameservers, DS Data) + * Removes form errors surrounding a form input + */ +function removeFormErrors(input, removeStaleAlerts=false){ + // Remove error message + let errorMessage = document.getElementById(`${input.id}__error-message`); + if (errorMessage) { + errorMessage.remove(); + }else{ + return + } + + // Remove error classes + if (input.classList.contains('usa-input--error')) { + input.classList.remove('usa-input--error'); + } + + // Get the form label + let label = document.querySelector(`label[for="${input.id}"]`); + if (label) { + label.classList.remove('usa-label--error'); + + // Remove error classes from parent div + let parentDiv = label.parentElement; + if (parentDiv) { + parentDiv.classList.remove('usa-form-group--error'); + } + } + + if (removeStaleAlerts){ + let staleAlerts = document.querySelectorAll(".usa-alert--error") + for (let alert of staleAlerts){ + // Don't remove the error associated with the input + if (alert.id !== `${input.id}--toast`) { + alert.remove() + } + } + } +} + +/** + * Prepare the namerservers and DS data forms delete buttons + * We will call this on the forms init, and also every time we add a form * */ function removeForm(e, formLabel, isNameserversForm, addButton, formIdentifier){ @@ -460,6 +527,7 @@ function hideDeletedForms() { let isNameserversForm = document.querySelector(".nameservers-form"); let isOtherContactsForm = document.querySelector(".other-contacts-form"); let isDsDataForm = document.querySelector(".ds-data-form"); + let isDotgovDomain = document.querySelector(".dotgov-domain-form"); // The Nameservers formset features 2 required and 11 optionals if (isNameserversForm) { cloneIndex = 2; @@ -472,6 +540,8 @@ function hideDeletedForms() { formLabel = "Organization contact"; container = document.querySelector("#other-employees"); formIdentifier = "other_contacts" + } else if (isDotgovDomain) { + formIdentifier = "dotgov_domain" } let totalForms = document.querySelector(`#id_${formIdentifier}-TOTAL_FORMS`); @@ -554,6 +624,7 @@ function hideDeletedForms() { // Reset the values of each input to blank inputs.forEach((input) => { input.classList.remove("usa-input--error"); + input.classList.remove("usa-input--success"); if (input.type === "text" || input.type === "number" || input.type === "password" || input.type === "email" || input.type === "tel") { input.value = ""; // Set the value to an empty string @@ -566,22 +637,25 @@ function hideDeletedForms() { let selects = newForm.querySelectorAll("select"); selects.forEach((select) => { select.classList.remove("usa-input--error"); + select.classList.remove("usa-input--success"); select.selectedIndex = 0; // Set the value to an empty string }); let labels = newForm.querySelectorAll("label"); labels.forEach((label) => { label.classList.remove("usa-label--error"); + label.classList.remove("usa-label--success"); }); let usaFormGroups = newForm.querySelectorAll(".usa-form-group"); usaFormGroups.forEach((usaFormGroup) => { usaFormGroup.classList.remove("usa-form-group--error"); + usaFormGroup.classList.remove("usa-form-group--success"); }); - // Remove any existing error messages - let usaErrorMessages = newForm.querySelectorAll(".usa-error-message"); - usaErrorMessages.forEach((usaErrorMessage) => { + // Remove any existing error and success messages + let usaMessages = newForm.querySelectorAll(".usa-error-message, .usa-alert"); + usaMessages.forEach((usaErrorMessage) => { let parentDiv = usaErrorMessage.closest('div'); if (parentDiv) { parentDiv.remove(); // Remove the parent div if it exists @@ -592,7 +666,8 @@ function hideDeletedForms() { // Attach click event listener on the delete buttons of the new form let newDeleteButton = newForm.querySelector(".delete-record"); - prepareNewDeleteButton(newDeleteButton, formLabel); + if (newDeleteButton) + prepareNewDeleteButton(newDeleteButton, formLabel); // Disable the add more button if we have 13 forms if (isNameserversForm && formNum == 13) { diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 02089ec6d..2f4121399 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -44,6 +44,22 @@ a.usa-button.disabled-link:focus { color: #454545 !important } +a.usa-button--unstyled.disabled-link, +a.usa-button--unstyled.disabled-link:hover, +a.usa-button--unstyled.disabled-link:focus { + cursor: not-allowed !important; + outline: none !important; + text-decoration: none !important; +} + +.usa-button--unstyled.disabled-button, +.usa-button--unstyled.disabled-link:hover, +.usa-button--unstyled.disabled-link:focus { + cursor: not-allowed !important; + outline: none !important; + text-decoration: none !important; +} + a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { color: color('white'); } diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index ba5ae22cc..f6378b555 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -142,6 +142,11 @@ urlpatterns = [ views.DomainApplicationDeleteView.as_view(http_method_names=["post"]), name="application-delete", ), + path( + "domain//users//delete", + views.DomainDeleteUserView.as_view(http_method_names=["post"]), + name="domain-user-delete", + ), ] # we normally would guard these with `if settings.DEBUG` but tests run with diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 85ce28bb6..1ee7e0036 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -420,7 +420,7 @@ class AlternativeDomainForm(RegistrarForm): alternative_domain = forms.CharField( required=False, - label="", + label="Alternative domain", ) diff --git a/src/registrar/migrations/0068_domainapplication_notes_domaininformation_notes.py b/src/registrar/migrations/0068_domainapplication_notes_domaininformation_notes.py new file mode 100644 index 000000000..ea94be77e --- /dev/null +++ b/src/registrar/migrations/0068_domainapplication_notes_domaininformation_notes.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2024-01-26 20:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0067_create_groups_v07"), + ] + + operations = [ + migrations.AddField( + model_name="domainapplication", + name="notes", + field=models.TextField(blank=True, help_text="Notes about this request", null=True), + ), + migrations.AddField( + model_name="domaininformation", + name="notes", + field=models.TextField(blank=True, help_text="Notes about the request", null=True), + ), + ] diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 30def9cfc..4c1a8e22c 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -558,6 +558,12 @@ class DomainApplication(TimeStampedModel): help_text="Date submitted", ) + notes = models.TextField( + null=True, + blank=True, + help_text="Notes about this request", + ) + def __str__(self): try: if self.requested_domain and self.requested_domain.name: @@ -707,7 +713,7 @@ class DomainApplication(TimeStampedModel): # copy the information from domainapplication into domaininformation DomainInformation = apps.get_model("registrar.DomainInformation") - DomainInformation.create_from_da(self, domain=created_domain) + DomainInformation.create_from_da(domain_application=self, domain=created_domain) # create the permission for the user UserDomainRole = apps.get_model("registrar.UserDomainRole") diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index bdff6061b..65d099e5a 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -1,4 +1,7 @@ from __future__ import annotations +from django.db import transaction + +from registrar.models.utility.domain_helper import DomainHelper from .domain_application import DomainApplication from .utility.time_stamped_model import TimeStampedModel @@ -202,6 +205,12 @@ class DomainInformation(TimeStampedModel): help_text="Acknowledged .gov acceptable use policy", ) + notes = models.TextField( + null=True, + blank=True, + help_text="Notes about the request", + ) + def __str__(self): try: if self.domain and self.domain.name: @@ -212,37 +221,63 @@ class DomainInformation(TimeStampedModel): return "" @classmethod - def create_from_da(cls, domain_application, domain=None): - """Takes in a DomainApplication dict and converts it into DomainInformation""" - da_dict = domain_application.to_dict() - # remove the id so one can be assinged on creation - da_id = da_dict.pop("id", None) + def create_from_da(cls, domain_application: DomainApplication, domain=None): + """Takes in a DomainApplication and converts it into DomainInformation""" + + # Throw an error if we get None - we can't create something from nothing + if domain_application is None: + raise ValueError("The provided DomainApplication is None") + + # Throw an error if the da doesn't have an id + if not hasattr(domain_application, "id"): + raise ValueError("The provided DomainApplication has no id") + # check if we have a record that corresponds with the domain # application, if so short circuit the create - domain_info = cls.objects.filter(domain_application__id=da_id).first() - if domain_info: - return domain_info - # the following information below is not needed in the domain information: - da_dict.pop("status", None) - da_dict.pop("current_websites", None) - da_dict.pop("investigator", None) - da_dict.pop("alternative_domains", None) - da_dict.pop("requested_domain", None) - da_dict.pop("approved_domain", None) - da_dict.pop("submission_date", None) - other_contacts = da_dict.pop("other_contacts", []) - domain_info = cls(**da_dict) - domain_info.domain_application = domain_application - # Save so the object now have PK - # (needed to process the manytomany below before, first) - domain_info.save() + existing_domain_info = cls.objects.filter(domain_application__id=domain_application.id).first() + if existing_domain_info: + return existing_domain_info - # Process the remaining "many to many" stuff - domain_info.other_contacts.add(*other_contacts) + # Get the fields that exist on both DomainApplication and DomainInformation + common_fields = DomainHelper.get_common_fields(DomainApplication, DomainInformation) + + # Get a list of all many_to_many relations on DomainInformation (needs to be saved differently) + info_many_to_many_fields = DomainInformation._get_many_to_many_fields() + + # Create a dictionary with only the common fields, and create a DomainInformation from it + da_dict = {} + da_many_to_many_dict = {} + for field in common_fields: + # If the field isn't many_to_many, populate the da_dict. + # If it is, populate da_many_to_many_dict as we need to save this later. + if hasattr(domain_application, field): + if field not in info_many_to_many_fields: + da_dict[field] = getattr(domain_application, field) + else: + da_many_to_many_dict[field] = getattr(domain_application, field).all() + + # Create a placeholder DomainInformation object + domain_info = DomainInformation(**da_dict) + + # Add the domain_application and domain fields + domain_info.domain_application = domain_application if domain: domain_info.domain = domain - domain_info.save() + + # Save the instance and set the many-to-many fields. + # Lumped under .atomic to ensure we don't make redundant DB calls. + # This bundles them all together, and then saves it in a single call. + with transaction.atomic(): + domain_info.save() + for field, value in da_many_to_many_dict.items(): + getattr(domain_info, field).set(value) + return domain_info + @staticmethod + def _get_many_to_many_fields(): + """Returns a set of each field.name that has the many to many relation""" + return {field.name for field in DomainInformation._meta.many_to_many} # type: ignore + class Meta: verbose_name_plural = "Domain information" diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py index 3267f0c93..230b23e16 100644 --- a/src/registrar/models/utility/domain_helper.py +++ b/src/registrar/models/utility/domain_helper.py @@ -1,5 +1,6 @@ import re - +from typing import Type +from django.db import models from django import forms from django.http import JsonResponse @@ -29,7 +30,6 @@ class DomainHelper: @classmethod def validate(cls, domain: str, blank_ok=False) -> str: """Attempt to determine if a domain name could be requested.""" - # Split into pieces for the linter domain = cls._validate_domain_string(domain, blank_ok) @@ -161,3 +161,29 @@ class DomainHelper: """Get the top level domain. Example: `gsa.gov` -> `gov`.""" parts = domain.rsplit(".") return parts[-1] if len(parts) > 1 else "" + + @staticmethod + def get_common_fields(model_1: Type[models.Model], model_2: Type[models.Model]): + """ + Returns a set of field names that two Django models have in common, excluding the 'id' field. + + Args: + model_1 (Type[models.Model]): The first Django model class. + model_2 (Type[models.Model]): The second Django model class. + + Returns: + Set[str]: A set of field names that both models share. + + Example: + If model_1 has fields {"id", "name", "color"} and model_2 has fields {"id", "color"}, + the function will return {"color"}. + """ + + # Get a list of the existing fields on model_1 and model_2 + model_1_fields = set(field.name for field in model_1._meta.get_fields() if field != "id") + model_2_fields = set(field.name for field in model_2._meta.get_fields() if field != "id") + + # Get the fields that exist on both DomainApplication and DomainInformation + common_fields = model_1_fields & model_2_fields + + return common_fields diff --git a/src/registrar/templates/application_dotgov_domain.html b/src/registrar/templates/application_dotgov_domain.html index 1838f33f4..155fdbeb4 100644 --- a/src/registrar/templates/application_dotgov_domain.html +++ b/src/registrar/templates/application_dotgov_domain.html @@ -11,7 +11,8 @@

-

Names that uniquely apply to your organization are likely to be approved over names that could also apply to other organizations. In most instances, this requires including your state’s two-letter abbreviation.

+

Names that uniquely apply to your organization are likely to be approved over names that could also apply to other organizations. + {% if not is_federal %}In most instances, this requires including your state’s two-letter abbreviation.{% endif %}

Requests for your organization’s initials or an abbreviated name might not be approved, but we encourage you to request the name you want.

@@ -48,16 +49,15 @@ {% endwith %} {% endwith %} {{ forms.1.management_form }} -
+

Alternative domains (optional)

@@ -66,23 +66,34 @@ you your first choice?

{% with attr_aria_describedby="alt_domain_instructions" %} - {# attr_validate / validate="domain" invokes code in get-gov.js #} - {# attr_auto_validate likewise triggers behavior in get-gov.js #} - {% with append_gov=True attr_validate="domain" attr_auto_validate=True %} - {% with add_class="blank-ok alternate-domain-input" %} - {% for form in forms.1 %} + {# Will probably want to remove blank-ok and do related cleanup when we implement delete #} + {% with attr_validate="domain" append_gov=True add_label_class="usa-sr-only" add_class="blank-ok alternate-domain-input" %} + {% for form in forms.1 %} +
{% input_with_errors form.alternative_domain %} - {% endfor %} - {% endwith %} +
+ {% endfor %} {% endwith %} {% endwith %} - -
+
+ +
+ + +

If you’re not sure this is the domain you want, that’s ok. You can change the domain later.

+ +
{% endblock %} diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index 524045fbe..965967072 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% load static form_helpers url_helpers %} -{% block title %}Request a .gov domain | {{form_titles|get_item:steps.current}} | {% endblock %} +{% block title %}{{form_titles|get_item:steps.current}} | Request a .gov | {% endblock %} {% block content %}
diff --git a/src/registrar/templates/dashboard_base.html b/src/registrar/templates/dashboard_base.html index 27b5ea717..6dd2ce8fd 100644 --- a/src/registrar/templates/dashboard_base.html +++ b/src/registrar/templates/dashboard_base.html @@ -3,22 +3,22 @@ {% block wrapper %}
- {% block messages %} - {% if messages %} -
    - {% for message in messages %} - - {{ message }} - - {% endfor %} -
- {% endif %} - {% endblock %} - {% block section_nav %}{% endblock %} {% block hero %}{% endblock %} - {% block content %}{% endblock %} + {% block content %} + {% block messages %} + {% if messages %} +
    + {% for message in messages %} +
  • + {{ message }} +
  • + {% endfor %} +
+ {% endif %} + {% endblock %} + {% endblock %}
{% block complementary %}{% endblock %}
diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 0eecd35b3..e295d2f7e 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -16,10 +16,8 @@
  • There is no limit to the number of domain managers you can add.
  • After adding a domain manager, an email invitation will be sent to that user with instructions on how to set up an account.
  • -
  • To remove a domain manager, contact us for - assistance.
  • All domain managers must keep their contact information updated and be responsive if contacted by the .gov team.
  • +
  • Domains must have at least one domain manager. You can’t remove yourself as a domain manager if you’re the only one assigned to this domain. Add another domain manager before you remove yourself from this domain.
  • {% if domain.permissions %} @@ -30,7 +28,8 @@ Email - Role + Role + Action @@ -40,6 +39,61 @@ {{ permission.user.email }} {{ permission.role|title }} + + {% if can_delete_users %} + + Remove + + {# Display a custom message if the user is trying to delete themselves #} + {% if permission.user.email == current_user_email %} +
    +
    + {% with domain_name=domain.name|force_escape %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager?" modal_description="You will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button_self|safe %} + {% endwith %} +
    +
    + {% else %} +
    +
    + {% with email=permission.user.email|default:permission.user|force_escape domain_name=domain.name|force_escape %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value=email|add:"?" modal_description=""|add:email|add:" will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} + {% endwith %} +
    +
    + {% endif %} + {% else %} + + {% endif %} + {% endfor %} @@ -66,8 +120,8 @@ Email Date created - Status - Action + Status + Action @@ -78,8 +132,9 @@ {{ invitation.created_at|date }} {{ invitation.status|title }} -
    - {% csrf_token %} + + + {% csrf_token %}
    diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index c7a005f97..2400e38f3 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -10,6 +10,9 @@ {# the entire logged in page goes here #}
    + {% block messages %} + {% include "includes/form_messages.html" %} + {% endblock %}

    Manage your domains

    diff --git a/src/registrar/templates/includes/form_messages.html b/src/registrar/templates/includes/form_messages.html index c7b704f67..59ecb4eaa 100644 --- a/src/registrar/templates/includes/form_messages.html +++ b/src/registrar/templates/includes/form_messages.html @@ -2,7 +2,7 @@ {% for message in messages %}

    - {{ message }} + {{ message }}
    diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 023e5319e..2865bf5c5 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -12,6 +12,7 @@ from typing import List, Dict from django.contrib.sessions.middleware import SessionMiddleware from django.conf import settings from django.contrib.auth import get_user_model, login +from django.utils.timezone import make_aware from registrar.models import ( Contact, @@ -643,7 +644,7 @@ class MockEppLib(TestCase): self, id, email, - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), pw="thisisnotapassword", ): fake = info.InfoContactResultData( @@ -681,7 +682,7 @@ class MockEppLib(TestCase): mockDataInfoDomain = fakedEppObject( "fakePw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)], hosts=["fake.host.com"], statuses=[ @@ -692,7 +693,7 @@ class MockEppLib(TestCase): ) mockDataExtensionDomain = fakedEppObject( "fakePw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)], hosts=["fake.host.com"], statuses=[ @@ -706,7 +707,7 @@ class MockEppLib(TestCase): ) InfoDomainWithContacts = fakedEppObject( "fakepw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[ common.DomainContact( contact="securityContact", @@ -731,7 +732,7 @@ class MockEppLib(TestCase): InfoDomainWithDefaultSecurityContact = fakedEppObject( "fakepw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[ common.DomainContact( contact="defaultSec", @@ -750,7 +751,7 @@ class MockEppLib(TestCase): ) InfoDomainWithVerisignSecurityContact = fakedEppObject( "fakepw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[ common.DomainContact( contact="defaultVeri", @@ -766,7 +767,7 @@ class MockEppLib(TestCase): InfoDomainWithDefaultTechnicalContact = fakedEppObject( "fakepw", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[ common.DomainContact( contact="defaultTech", @@ -791,14 +792,14 @@ class MockEppLib(TestCase): infoDomainNoContact = fakedEppObject( "security", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[], hosts=["fake.host.com"], ) infoDomainThreeHosts = fakedEppObject( "my-nameserver.gov", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[], hosts=[ "ns1.my-nameserver-1.com", @@ -809,25 +810,25 @@ class MockEppLib(TestCase): infoDomainNoHost = fakedEppObject( "my-nameserver.gov", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[], hosts=[], ) infoDomainTwoHosts = fakedEppObject( "my-nameserver.gov", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[], hosts=["ns1.my-nameserver-1.com", "ns1.my-nameserver-2.com"], ) mockDataInfoHosts = fakedEppObject( "lastPw", - cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 8, 25, 19, 45, 35)), addrs=[common.Ip(addr="1.2.3.4"), common.Ip(addr="2.3.4.5")], ) - mockDataHostChange = fakedEppObject("lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35)) + mockDataHostChange = fakedEppObject("lastPw", cr_date=make_aware(datetime.datetime(2023, 8, 25, 19, 45, 35))) addDsData1 = { "keyTag": 1234, "alg": 3, @@ -859,7 +860,7 @@ class MockEppLib(TestCase): infoDomainHasIP = fakedEppObject( "nameserverwithip.gov", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[ common.DomainContact( contact="securityContact", @@ -884,7 +885,7 @@ class MockEppLib(TestCase): justNameserver = fakedEppObject( "justnameserver.com", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[ common.DomainContact( contact="securityContact", @@ -907,7 +908,7 @@ class MockEppLib(TestCase): infoDomainCheckHostIPCombo = fakedEppObject( "nameserversubdomain.gov", - cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)), contacts=[], hosts=[ "ns1.nameserversubdomain.gov", diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index f88e25c2f..86cc287e8 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -59,22 +59,22 @@ class TestDomainAdmin(MockEppLib): """ Make sure the short name is displaying in admin on the list page """ - self.client.force_login(self.superuser) - application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): + with less_console_noise(): + self.client.force_login(self.superuser) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): application.approve() - response = self.client.get("/admin/registrar/domain/") + response = self.client.get("/admin/registrar/domain/") - # There are 3 template references to Federal (3) plus one reference in the table - # for our actual application - self.assertContains(response, "Federal", count=4) - # This may be a bit more robust - self.assertContains(response, 'Federal', count=1) - # Now let's make sure the long description does not exist - self.assertNotContains(response, "Federal: an agency of the U.S. government") + # There are 3 template references to Federal (3) plus one reference in the table + # for our actual application + self.assertContains(response, "Federal", count=4) + # This may be a bit more robust + self.assertContains(response, 'Federal', count=1) + # Now let's make sure the long description does not exist + self.assertNotContains(response, "Federal: an agency of the U.S. government") @skip("Why did this test stop working, and is is a good test") def test_place_and_remove_hold(self): @@ -120,40 +120,37 @@ class TestDomainAdmin(MockEppLib): Then a user-friendly success message is returned for displaying on the web And `state` is et to `DELETED` """ - domain = create_ready_domain() - # Put in client hold - domain.place_client_hold() - p = "userpass" - self.client.login(username="staffuser", password=p) - - # 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 - - 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, + with less_console_noise(): + domain = create_ready_domain() + # Put in client hold + domain.place_client_hold() + p = "userpass" + self.client.login(username="staffuser", password=p) + # Ensure everything is displaying correctly + response = self.client.get( + "/admin/registrar/domain/{}/change/".format(domain.pk), + follow=True, ) - - self.assertEqual(domain.state, Domain.State.DELETED) + 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 + 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) def test_deletion_ready_fsm_failure(self): """ @@ -162,38 +159,36 @@ class TestDomainAdmin(MockEppLib): Then a user-friendly error message is returned for displaying on the web And `state` is not set to `DELETED` """ - domain = create_ready_domain() - p = "userpass" - self.client.login(username="staffuser", password=p) - - # 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, + with less_console_noise(): + domain = create_ready_domain() + p = "userpass" + self.client.login(username="staffuser", password=p) + # 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) @@ -205,62 +200,57 @@ class TestDomainAdmin(MockEppLib): 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() - p = "userpass" - self.client.login(username="staffuser", password=p) - - # 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, + with less_console_noise(): + domain = create_ready_domain() + # Put in client hold + domain.place_client_hold() + p = "userpass" + self.client.login(username="staffuser", password=p) + # Ensure everything is displaying correctly + response = self.client.get( + "/admin/registrar/domain/{}/change/".format(domain.pk), + follow=True, ) - - 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(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) + 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) @skip("Waiting on epp lib to implement") def test_place_and_remove_hold_epp(self): @@ -624,6 +614,7 @@ class TestDomainApplicationAdmin(MockEppLib): "anything_else", "is_policy_acknowledged", "submission_date", + "notes", "current_websites", "other_contacts", "alternative_domains", @@ -641,6 +632,7 @@ class TestDomainApplicationAdmin(MockEppLib): "creator", "about_your_organization", "requested_domain", + "approved_domain", "alternative_domains", "purpose", "submitter", @@ -1066,7 +1058,7 @@ class DomainInvitationAdminTest(TestCase): self.assertContains(response, retrieved_html, count=1) -class DomainInformationAdminTest(TestCase): +class TestDomainInformationAdmin(TestCase): def setUp(self): """Setup environment for a mock admin user""" self.site = AdminSite() @@ -1074,6 +1066,7 @@ class DomainInformationAdminTest(TestCase): self.admin = DomainInformationAdmin(model=DomainInformation, admin_site=self.site) self.client = Client(HTTP_HOST="localhost:8080") self.superuser = create_superuser() + self.staffuser = create_user() self.mock_data_generator = AuditedAdminMockData() self.test_helper = GenericTestHelper( @@ -1117,6 +1110,27 @@ class DomainInformationAdminTest(TestCase): Contact.objects.all().delete() User.objects.all().delete() + def test_readonly_fields_for_analyst(self): + """Ensures that analysts have their permissions setup correctly""" + request = self.factory.get("/") + request.user = self.staffuser + + readonly_fields = self.admin.get_readonly_fields(request) + + expected_fields = [ + "creator", + "type_of_work", + "more_organization_information", + "domain", + "domain_application", + "submitter", + "no_other_contacts_rationale", + "anything_else", + "is_policy_acknowledged", + ] + + self.assertEqual(readonly_fields, expected_fields) + def test_domain_sortable(self): """Tests if DomainInformation sorts by domain correctly""" p = "adminpass" @@ -1281,64 +1295,62 @@ class ListHeaderAdminTest(TestCase): self.superuser = create_superuser() def test_changelist_view(self): - # Have to get creative to get past linter - p = "adminpass" - self.client.login(username="superuser", password=p) - - # Mock a user - user = mock_user() - - # Make the request using the Client class - # which handles CSRF - # Follow=True handles the redirect - response = self.client.get( - "/admin/registrar/domainapplication/", - { - "status__exact": "started", - "investigator__id__exact": user.id, - "q": "Hello", - }, - follow=True, - ) - - # Assert that the filters and search_query are added to the extra_context - self.assertIn("filters", response.context) - self.assertIn("search_query", response.context) - # Assert the content of filters and search_query - filters = response.context["filters"] - search_query = response.context["search_query"] - self.assertEqual(search_query, "Hello") - self.assertEqual( - filters, - [ - {"parameter_name": "status", "parameter_value": "started"}, + with less_console_noise(): + # Have to get creative to get past linter + p = "adminpass" + self.client.login(username="superuser", password=p) + # Mock a user + user = mock_user() + # Make the request using the Client class + # which handles CSRF + # Follow=True handles the redirect + response = self.client.get( + "/admin/registrar/domainapplication/", { - "parameter_name": "investigator", - "parameter_value": user.first_name + " " + user.last_name, + "status__exact": "started", + "investigator__id__exact": user.id, + "q": "Hello", }, - ], - ) + follow=True, + ) + # Assert that the filters and search_query are added to the extra_context + self.assertIn("filters", response.context) + self.assertIn("search_query", response.context) + # Assert the content of filters and search_query + filters = response.context["filters"] + search_query = response.context["search_query"] + self.assertEqual(search_query, "Hello") + self.assertEqual( + filters, + [ + {"parameter_name": "status", "parameter_value": "started"}, + { + "parameter_name": "investigator", + "parameter_value": user.first_name + " " + user.last_name, + }, + ], + ) def test_get_filters(self): - # Create a mock request object - request = self.factory.get("/admin/yourmodel/") - # Set the GET parameters for testing - request.GET = { - "status": "started", - "investigator": "Jeff Lebowski", - "q": "search_value", - } - # Call the get_filters method - filters = self.admin.get_filters(request) - - # Assert the filters extracted from the request GET - self.assertEqual( - filters, - [ - {"parameter_name": "status", "parameter_value": "started"}, - {"parameter_name": "investigator", "parameter_value": "Jeff Lebowski"}, - ], - ) + with less_console_noise(): + # Create a mock request object + request = self.factory.get("/admin/yourmodel/") + # Set the GET parameters for testing + request.GET = { + "status": "started", + "investigator": "Jeff Lebowski", + "q": "search_value", + } + # Call the get_filters method + filters = self.admin.get_filters(request) + # Assert the filters extracted from the request GET + self.assertEqual( + filters, + [ + {"parameter_name": "status", "parameter_value": "started"}, + {"parameter_name": "investigator", "parameter_value": "Jeff Lebowski"}, + ], + ) def tearDown(self): # delete any applications too @@ -1777,42 +1789,38 @@ class ContactAdminTest(TestCase): def test_change_view_for_joined_contact_five_or_more(self): """Create a contact, join it to 5 domain requests. The 6th join will be a user. Assert that the warning on the contact form lists 5 joins and a '1 more' ellispsis.""" - - self.client.force_login(self.superuser) - - # Create an instance of the model - # join it to 5 domain requests. The 6th join will be a user. - contact, _ = Contact.objects.get_or_create(user=self.staffuser) - application1 = completed_application(submitter=contact, name="city1.gov") - application2 = completed_application(submitter=contact, name="city2.gov") - application3 = completed_application(submitter=contact, name="city3.gov") - application4 = completed_application(submitter=contact, name="city4.gov") - application5 = completed_application(submitter=contact, name="city5.gov") - - with patch("django.contrib.messages.warning") as mock_warning: - # Use the test client to simulate the request - response = self.client.get(reverse("admin:registrar_contact_change", args=[contact.pk])) - - logger.info(mock_warning) - - # Assert that the error message was called with the correct argument - # Note: The 6th join will be a user. - mock_warning.assert_called_once_with( - response.wsgi_request, - "" - "

    And 1 more...

    ", - ) + with less_console_noise(): + self.client.force_login(self.superuser) + # Create an instance of the model + # join it to 5 domain requests. The 6th join will be a user. + contact, _ = Contact.objects.get_or_create(user=self.staffuser) + application1 = completed_application(submitter=contact, name="city1.gov") + application2 = completed_application(submitter=contact, name="city2.gov") + application3 = completed_application(submitter=contact, name="city3.gov") + application4 = completed_application(submitter=contact, name="city4.gov") + application5 = completed_application(submitter=contact, name="city5.gov") + with patch("django.contrib.messages.warning") as mock_warning: + # Use the test client to simulate the request + response = self.client.get(reverse("admin:registrar_contact_change", args=[contact.pk])) + logger.debug(mock_warning) + # Assert that the error message was called with the correct argument + # Note: The 6th join will be a user. + mock_warning.assert_called_once_with( + response.wsgi_request, + "" + "

    And 1 more...

    ", + ) def tearDown(self): DomainApplication.objects.all().delete() diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index 06886ba66..34178e262 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -1,5 +1,6 @@ import copy -import datetime +from datetime import date, datetime, time +from django.utils import timezone from django.test import TestCase @@ -17,7 +18,7 @@ from django.core.management import call_command from unittest.mock import patch, call from epplibwrapper import commands, common -from .common import MockEppLib +from .common import MockEppLib, less_console_noise class TestPopulateFirstReady(TestCase): @@ -33,7 +34,9 @@ class TestPopulateFirstReady(TestCase): self.unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN) # Set a ready_at date for testing purposes - self.ready_at_date = datetime.date(2022, 12, 31) + self.ready_at_date = date(2022, 12, 31) + _ready_at_datetime = datetime.combine(self.ready_at_date, time.min) + self.ready_at_date_tz_aware = timezone.make_aware(_ready_at_datetime, timezone=timezone.utc) def tearDown(self): """Deletes all DB objects related to migrations""" @@ -49,122 +52,103 @@ class TestPopulateFirstReady(TestCase): The 'call_command' function from Django's management framework is then used to execute the populate_first_ready command with the specified arguments. """ - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command("populate_first_ready") + with less_console_noise(): + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command("populate_first_ready") def test_populate_first_ready_state_ready(self): """ Tests that the populate_first_ready works as expected for the state 'ready' """ - # Set the created at date - self.ready_domain.created_at = self.ready_at_date - self.ready_domain.save() - - desired_domain = copy.deepcopy(self.ready_domain) - - desired_domain.first_ready = self.ready_at_date - - # Run the expiration date script - self.run_populate_first_ready() - - self.assertEqual(desired_domain, self.ready_domain) - - # Explicitly test the first_ready date - first_ready = Domain.objects.filter(name="fakeready.gov").get().first_ready - self.assertEqual(first_ready, self.ready_at_date) + with less_console_noise(): + # Set the created at date + self.ready_domain.created_at = self.ready_at_date_tz_aware + self.ready_domain.save() + desired_domain = copy.deepcopy(self.ready_domain) + desired_domain.first_ready = self.ready_at_date + # Run the expiration date script + self.run_populate_first_ready() + self.assertEqual(desired_domain, self.ready_domain) + # Explicitly test the first_ready date + first_ready = Domain.objects.filter(name="fakeready.gov").get().first_ready + self.assertEqual(first_ready, self.ready_at_date) def test_populate_first_ready_state_deleted(self): """ Tests that the populate_first_ready works as expected for the state 'deleted' """ - # Set the created at date - self.deleted_domain.created_at = self.ready_at_date - self.deleted_domain.save() - - desired_domain = copy.deepcopy(self.deleted_domain) - - desired_domain.first_ready = self.ready_at_date - - # Run the expiration date script - self.run_populate_first_ready() - - self.assertEqual(desired_domain, self.deleted_domain) - - # Explicitly test the first_ready date - first_ready = Domain.objects.filter(name="fakedeleted.gov").get().first_ready - self.assertEqual(first_ready, self.ready_at_date) + with less_console_noise(): + # Set the created at date + self.deleted_domain.created_at = self.ready_at_date_tz_aware + self.deleted_domain.save() + desired_domain = copy.deepcopy(self.deleted_domain) + desired_domain.first_ready = self.ready_at_date + # Run the expiration date script + self.run_populate_first_ready() + self.assertEqual(desired_domain, self.deleted_domain) + # Explicitly test the first_ready date + first_ready = Domain.objects.filter(name="fakedeleted.gov").get().first_ready + self.assertEqual(first_ready, self.ready_at_date) def test_populate_first_ready_state_dns_needed(self): """ Tests that the populate_first_ready doesn't make changes when a domain's state is 'dns_needed' """ - # Set the created at date - self.dns_needed_domain.created_at = self.ready_at_date - self.dns_needed_domain.save() - - desired_domain = copy.deepcopy(self.dns_needed_domain) - - desired_domain.first_ready = None - - # Run the expiration date script - self.run_populate_first_ready() - - current_domain = self.dns_needed_domain - # The object should largely be unaltered (does not test first_ready) - self.assertEqual(desired_domain, current_domain) - - first_ready = Domain.objects.filter(name="fakedns.gov").get().first_ready - - # Explicitly test the first_ready date - self.assertNotEqual(first_ready, self.ready_at_date) - self.assertEqual(first_ready, None) + with less_console_noise(): + # Set the created at date + self.dns_needed_domain.created_at = self.ready_at_date_tz_aware + self.dns_needed_domain.save() + desired_domain = copy.deepcopy(self.dns_needed_domain) + desired_domain.first_ready = None + # Run the expiration date script + self.run_populate_first_ready() + current_domain = self.dns_needed_domain + # The object should largely be unaltered (does not test first_ready) + self.assertEqual(desired_domain, current_domain) + first_ready = Domain.objects.filter(name="fakedns.gov").get().first_ready + # Explicitly test the first_ready date + self.assertNotEqual(first_ready, self.ready_at_date) + self.assertEqual(first_ready, None) def test_populate_first_ready_state_on_hold(self): """ Tests that the populate_first_ready works as expected for the state 'on_hold' """ - self.hold_domain.created_at = self.ready_at_date - self.hold_domain.save() - - desired_domain = copy.deepcopy(self.hold_domain) - desired_domain.first_ready = self.ready_at_date - - # Run the update first ready_at script - self.run_populate_first_ready() - - current_domain = self.hold_domain - self.assertEqual(desired_domain, current_domain) - - # Explicitly test the first_ready date - first_ready = Domain.objects.filter(name="fakehold.gov").get().first_ready - self.assertEqual(first_ready, self.ready_at_date) + with less_console_noise(): + self.hold_domain.created_at = self.ready_at_date_tz_aware + self.hold_domain.save() + desired_domain = copy.deepcopy(self.hold_domain) + desired_domain.first_ready = self.ready_at_date + # Run the update first ready_at script + self.run_populate_first_ready() + current_domain = self.hold_domain + self.assertEqual(desired_domain, current_domain) + # Explicitly test the first_ready date + first_ready = Domain.objects.filter(name="fakehold.gov").get().first_ready + self.assertEqual(first_ready, self.ready_at_date) def test_populate_first_ready_state_unknown(self): """ Tests that the populate_first_ready works as expected for the state 'unknown' """ - # Set the created at date - self.unknown_domain.created_at = self.ready_at_date - self.unknown_domain.save() - - desired_domain = copy.deepcopy(self.unknown_domain) - desired_domain.first_ready = None - - # Run the expiration date script - self.run_populate_first_ready() - - current_domain = self.unknown_domain - - # The object should largely be unaltered (does not test first_ready) - self.assertEqual(desired_domain, current_domain) - - # Explicitly test the first_ready date - first_ready = Domain.objects.filter(name="fakeunknown.gov").get().first_ready - self.assertNotEqual(first_ready, self.ready_at_date) - self.assertEqual(first_ready, None) + with less_console_noise(): + # Set the created at date + self.unknown_domain.created_at = self.ready_at_date_tz_aware + self.unknown_domain.save() + desired_domain = copy.deepcopy(self.unknown_domain) + desired_domain.first_ready = None + # Run the expiration date script + self.run_populate_first_ready() + current_domain = self.unknown_domain + # The object should largely be unaltered (does not test first_ready) + self.assertEqual(desired_domain, current_domain) + # Explicitly test the first_ready date + first_ready = Domain.objects.filter(name="fakeunknown.gov").get().first_ready + self.assertNotEqual(first_ready, self.ready_at_date) + self.assertEqual(first_ready, None) class TestPatchAgencyInfo(TestCase): @@ -185,7 +169,8 @@ class TestPatchAgencyInfo(TestCase): @patch("registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", return_value=True) def call_patch_federal_agency_info(self, mock_prompt): """Calls the patch_federal_agency_info command and mimics a keypress""" - call_command("patch_federal_agency_info", "registrar/tests/data/fake_current_full.csv", debug=True) + with less_console_noise(): + call_command("patch_federal_agency_info", "registrar/tests/data/fake_current_full.csv", debug=True) def test_patch_agency_info(self): """ @@ -194,17 +179,14 @@ class TestPatchAgencyInfo(TestCase): of a `DomainInformation` object when the corresponding `TransitionDomain` object has a valid `federal_agency`. """ - - # Ensure that the federal_agency is None - self.assertEqual(self.domain_info.federal_agency, None) - - self.call_patch_federal_agency_info() - - # Reload the domain_info object from the database - self.domain_info.refresh_from_db() - - # Check that the federal_agency field was updated - self.assertEqual(self.domain_info.federal_agency, "test agency") + with less_console_noise(): + # Ensure that the federal_agency is None + self.assertEqual(self.domain_info.federal_agency, None) + self.call_patch_federal_agency_info() + # Reload the domain_info object from the database + self.domain_info.refresh_from_db() + # Check that the federal_agency field was updated + self.assertEqual(self.domain_info.federal_agency, "test agency") def test_patch_agency_info_skip(self): """ @@ -213,21 +195,18 @@ class TestPatchAgencyInfo(TestCase): of a `DomainInformation` object when the corresponding `TransitionDomain` object does not exist. """ - # Set federal_agency to None to simulate a skip - self.transition_domain.federal_agency = None - self.transition_domain.save() - - with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context: - self.call_patch_federal_agency_info() - - # Check that the correct log message was output - self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0]) - - # Reload the domain_info object from the database - self.domain_info.refresh_from_db() - - # Check that the federal_agency field was not updated - self.assertIsNone(self.domain_info.federal_agency) + with less_console_noise(): + # Set federal_agency to None to simulate a skip + self.transition_domain.federal_agency = None + self.transition_domain.save() + with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context: + self.call_patch_federal_agency_info() + # Check that the correct log message was output + self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0]) + # Reload the domain_info object from the database + self.domain_info.refresh_from_db() + # Check that the federal_agency field was not updated + self.assertIsNone(self.domain_info.federal_agency) def test_patch_agency_info_skip_updates_data(self): """ @@ -235,25 +214,21 @@ class TestPatchAgencyInfo(TestCase): updates the DomainInformation object, because a record exists in the provided current-full.csv file. """ - # Set federal_agency to None to simulate a skip - self.transition_domain.federal_agency = None - self.transition_domain.save() - - # Change the domain name to something parsable in the .csv - self.domain.name = "cdomain1.gov" - self.domain.save() - - with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context: - self.call_patch_federal_agency_info() - - # Check that the correct log message was output - self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0]) - - # Reload the domain_info object from the database - self.domain_info.refresh_from_db() - - # Check that the federal_agency field was not updated - self.assertEqual(self.domain_info.federal_agency, "World War I Centennial Commission") + with less_console_noise(): + # Set federal_agency to None to simulate a skip + self.transition_domain.federal_agency = None + self.transition_domain.save() + # Change the domain name to something parsable in the .csv + self.domain.name = "cdomain1.gov" + self.domain.save() + with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context: + self.call_patch_federal_agency_info() + # Check that the correct log message was output + self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0]) + # Reload the domain_info object from the database + self.domain_info.refresh_from_db() + # Check that the federal_agency field was not updated + self.assertEqual(self.domain_info.federal_agency, "World War I Centennial Commission") def test_patch_agency_info_skips_valid_domains(self): """ @@ -261,20 +236,17 @@ class TestPatchAgencyInfo(TestCase): does not update the `federal_agency` field of a `DomainInformation` object """ - self.domain_info.federal_agency = "unchanged" - self.domain_info.save() - - with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="INFO") as context: - self.call_patch_federal_agency_info() - - # Check that the correct log message was output - self.assertIn("FINISHED", context.output[1]) - - # Reload the domain_info object from the database - self.domain_info.refresh_from_db() - - # Check that the federal_agency field was not updated - self.assertEqual(self.domain_info.federal_agency, "unchanged") + with less_console_noise(): + self.domain_info.federal_agency = "unchanged" + self.domain_info.save() + with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="INFO") as context: + self.call_patch_federal_agency_info() + # Check that the correct log message was output + self.assertIn("FINISHED", context.output[1]) + # Reload the domain_info object from the database + self.domain_info.refresh_from_db() + # Check that the federal_agency field was not updated + self.assertEqual(self.domain_info.federal_agency, "unchanged") class TestExtendExpirationDates(MockEppLib): @@ -283,39 +255,37 @@ class TestExtendExpirationDates(MockEppLib): super().setUp() # Create a valid domain that is updatable Domain.objects.get_or_create( - name="waterbutpurple.gov", state=Domain.State.READY, expiration_date=datetime.date(2023, 11, 15) + name="waterbutpurple.gov", state=Domain.State.READY, expiration_date=date(2023, 11, 15) ) TransitionDomain.objects.get_or_create( username="testytester@mail.com", domain_name="waterbutpurple.gov", - epp_expiration_date=datetime.date(2023, 11, 15), + epp_expiration_date=date(2023, 11, 15), ) # Create a domain with an invalid expiration date - Domain.objects.get_or_create( - name="fake.gov", state=Domain.State.READY, expiration_date=datetime.date(2022, 5, 25) - ) + Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY, expiration_date=date(2022, 5, 25)) TransitionDomain.objects.get_or_create( username="themoonisactuallycheese@mail.com", domain_name="fake.gov", - epp_expiration_date=datetime.date(2022, 5, 25), + epp_expiration_date=date(2022, 5, 25), ) # Create a domain with an invalid state Domain.objects.get_or_create( - name="fakeneeded.gov", state=Domain.State.DNS_NEEDED, expiration_date=datetime.date(2023, 11, 15) + name="fakeneeded.gov", state=Domain.State.DNS_NEEDED, expiration_date=date(2023, 11, 15) ) TransitionDomain.objects.get_or_create( username="fakeneeded@mail.com", domain_name="fakeneeded.gov", - epp_expiration_date=datetime.date(2023, 11, 15), + epp_expiration_date=date(2023, 11, 15), ) # Create a domain with a date greater than the maximum Domain.objects.get_or_create( - name="fakemaximum.gov", state=Domain.State.READY, expiration_date=datetime.date(2024, 12, 31) + name="fakemaximum.gov", state=Domain.State.READY, expiration_date=date(2024, 12, 31) ) TransitionDomain.objects.get_or_create( username="fakemaximum@mail.com", domain_name="fakemaximum.gov", - epp_expiration_date=datetime.date(2024, 12, 31), + epp_expiration_date=date(2024, 12, 31), ) def tearDown(self): @@ -338,83 +308,82 @@ class TestExtendExpirationDates(MockEppLib): The 'call_command' function from Django's management framework is then used to execute the extend_expiration_dates command with the specified arguments. """ - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command("extend_expiration_dates") + with less_console_noise(): + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command("extend_expiration_dates") def test_extends_expiration_date_correctly(self): """ Tests that the extend_expiration_dates method extends dates as expected """ - desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get() - desired_domain.expiration_date = datetime.date(2024, 11, 15) - - # Run the expiration date script - self.run_extend_expiration_dates() - - current_domain = Domain.objects.filter(name="waterbutpurple.gov").get() - - self.assertEqual(desired_domain, current_domain) - # Explicitly test the expiration date - self.assertEqual(current_domain.expiration_date, datetime.date(2024, 11, 15)) + with less_console_noise(): + desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get() + desired_domain.expiration_date = date(2024, 11, 15) + # Run the expiration date script + self.run_extend_expiration_dates() + current_domain = Domain.objects.filter(name="waterbutpurple.gov").get() + self.assertEqual(desired_domain, current_domain) + # Explicitly test the expiration date + self.assertEqual(current_domain.expiration_date, date(2024, 11, 15)) def test_extends_expiration_date_skips_non_current(self): """ Tests that the extend_expiration_dates method correctly skips domains with an expiration date less than a certain threshold. """ - desired_domain = Domain.objects.filter(name="fake.gov").get() - desired_domain.expiration_date = datetime.date(2022, 5, 25) - - # Run the expiration date script - self.run_extend_expiration_dates() - - current_domain = Domain.objects.filter(name="fake.gov").get() - self.assertEqual(desired_domain, current_domain) - - # Explicitly test the expiration date. The extend_expiration_dates script - # will skip all dates less than date(2023, 11, 15), meaning that this domain - # should not be affected by the change. - self.assertEqual(current_domain.expiration_date, datetime.date(2022, 5, 25)) + with less_console_noise(): + desired_domain = Domain.objects.filter(name="fake.gov").get() + desired_domain.expiration_date = date(2022, 5, 25) + # Run the expiration date script + self.run_extend_expiration_dates() + current_domain = Domain.objects.filter(name="fake.gov").get() + self.assertEqual(desired_domain, current_domain) + # Explicitly test the expiration date. The extend_expiration_dates script + # will skip all dates less than date(2023, 11, 15), meaning that this domain + # should not be affected by the change. + self.assertEqual(current_domain.expiration_date, date(2022, 5, 25)) def test_extends_expiration_date_skips_maximum_date(self): """ Tests that the extend_expiration_dates method correctly skips domains with an expiration date more than a certain threshold. """ - desired_domain = Domain.objects.filter(name="fakemaximum.gov").get() - desired_domain.expiration_date = datetime.date(2024, 12, 31) + with less_console_noise(): + desired_domain = Domain.objects.filter(name="fakemaximum.gov").get() + desired_domain.expiration_date = date(2024, 12, 31) - # Run the expiration date script - self.run_extend_expiration_dates() + # Run the expiration date script + self.run_extend_expiration_dates() - current_domain = Domain.objects.filter(name="fakemaximum.gov").get() - self.assertEqual(desired_domain, current_domain) + current_domain = Domain.objects.filter(name="fakemaximum.gov").get() + self.assertEqual(desired_domain, current_domain) - # Explicitly test the expiration date. The extend_expiration_dates script - # will skip all dates less than date(2023, 11, 15), meaning that this domain - # should not be affected by the change. - self.assertEqual(current_domain.expiration_date, datetime.date(2024, 12, 31)) + # Explicitly test the expiration date. The extend_expiration_dates script + # will skip all dates less than date(2023, 11, 15), meaning that this domain + # should not be affected by the change. + self.assertEqual(current_domain.expiration_date, date(2024, 12, 31)) def test_extends_expiration_date_skips_non_ready(self): """ Tests that the extend_expiration_dates method correctly skips domains not in the state "ready" """ - desired_domain = Domain.objects.filter(name="fakeneeded.gov").get() - desired_domain.expiration_date = datetime.date(2023, 11, 15) + with less_console_noise(): + desired_domain = Domain.objects.filter(name="fakeneeded.gov").get() + desired_domain.expiration_date = date(2023, 11, 15) - # Run the expiration date script - self.run_extend_expiration_dates() + # Run the expiration date script + self.run_extend_expiration_dates() - current_domain = Domain.objects.filter(name="fakeneeded.gov").get() - self.assertEqual(desired_domain, current_domain) + current_domain = Domain.objects.filter(name="fakeneeded.gov").get() + self.assertEqual(desired_domain, current_domain) - # Explicitly test the expiration date. The extend_expiration_dates script - # will skip all dates less than date(2023, 11, 15), meaning that this domain - # should not be affected by the change. - self.assertEqual(current_domain.expiration_date, datetime.date(2023, 11, 15)) + # Explicitly test the expiration date. The extend_expiration_dates script + # will skip all dates less than date(2023, 11, 15), meaning that this domain + # should not be affected by the change. + self.assertEqual(current_domain.expiration_date, date(2023, 11, 15)) def test_extends_expiration_date_idempotent(self): """ @@ -423,26 +392,21 @@ class TestExtendExpirationDates(MockEppLib): Verifies that running the method multiple times does not change the expiration date of a domain beyond the initial extension. """ - desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get() - desired_domain.expiration_date = datetime.date(2024, 11, 15) - - # Run the expiration date script - self.run_extend_expiration_dates() - - current_domain = Domain.objects.filter(name="waterbutpurple.gov").get() - self.assertEqual(desired_domain, current_domain) - - # Explicitly test the expiration date - self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15)) - - # Run the expiration date script again - self.run_extend_expiration_dates() - - # The old domain shouldn't have changed - self.assertEqual(desired_domain, current_domain) - - # Explicitly test the expiration date - should be the same - self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15)) + with less_console_noise(): + desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get() + desired_domain.expiration_date = date(2024, 11, 15) + # Run the expiration date script + self.run_extend_expiration_dates() + current_domain = Domain.objects.filter(name="waterbutpurple.gov").get() + self.assertEqual(desired_domain, current_domain) + # Explicitly test the expiration date + self.assertEqual(desired_domain.expiration_date, date(2024, 11, 15)) + # Run the expiration date script again + self.run_extend_expiration_dates() + # The old domain shouldn't have changed + self.assertEqual(desired_domain, current_domain) + # Explicitly test the expiration date - should be the same + self.assertEqual(desired_domain.expiration_date, date(2024, 11, 15)) class TestDiscloseEmails(MockEppLib): @@ -461,39 +425,41 @@ class TestDiscloseEmails(MockEppLib): The 'call_command' function from Django's management framework is then used to execute the disclose_security_emails command. """ - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command("disclose_security_emails") + with less_console_noise(): + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command("disclose_security_emails") def test_disclose_security_emails(self): """ Tests that command disclose_security_emails runs successfully with appropriate EPP calll to UpdateContact. """ - domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY) - expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = domain - expectedSecContact.email = "123@mail.gov" - # set domain security email to 123@mail.gov instead of default email - domain.security_contact = expectedSecContact - self.run_disclose_security_emails() + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY) + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = domain + expectedSecContact.email = "123@mail.gov" + # set domain security email to 123@mail.gov instead of default email + domain.security_contact = expectedSecContact + self.run_disclose_security_emails() - # running disclose_security_emails sends EPP call UpdateContact with disclose - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.UpdateContact( - id=domain.security_contact.registry_id, - postal_info=domain._make_epp_contact_postal_info(contact=domain.security_contact), - email=domain.security_contact.email, - voice=domain.security_contact.voice, - fax=domain.security_contact.fax, - auth_info=common.ContactAuthInfo(pw="2fooBAR123fooBaz"), - disclose=domain._disclose_fields(contact=domain.security_contact), - ), - cleaned=True, - ) - ] - ) + # running disclose_security_emails sends EPP call UpdateContact with disclose + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateContact( + id=domain.security_contact.registry_id, + postal_info=domain._make_epp_contact_postal_info(contact=domain.security_contact), + email=domain.security_contact.email, + voice=domain.security_contact.voice, + fax=domain.security_contact.fax, + auth_info=common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + disclose=domain._disclose_fields(contact=domain.security_contact), + ), + cleaned=True, + ) + ] + ) diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index d0005cbd5..294ec70af 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -60,127 +60,134 @@ class TestDomainApplication(TestCase): def assertNotRaises(self, exception_type): """Helper method for testing allowed transitions.""" - return self.assertRaises(Exception, None, exception_type) + with less_console_noise(): + return self.assertRaises(Exception, None, exception_type) def test_empty_create_fails(self): """Can't create a completely empty domain application. NOTE: something about theexception this test raises messes up with the atomic block in a custom tearDown method for the parent test class.""" - with self.assertRaisesRegex(IntegrityError, "creator"): - DomainApplication.objects.create() + with less_console_noise(): + with self.assertRaisesRegex(IntegrityError, "creator"): + DomainApplication.objects.create() def test_minimal_create(self): """Can create with just a creator.""" - user, _ = User.objects.get_or_create(username="testy") - application = DomainApplication.objects.create(creator=user) - self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED) + with less_console_noise(): + user, _ = User.objects.get_or_create(username="testy") + application = DomainApplication.objects.create(creator=user) + self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED) def test_full_create(self): """Can create with all fields.""" - user, _ = User.objects.get_or_create(username="testy") - contact = Contact.objects.create() - com_website, _ = Website.objects.get_or_create(website="igorville.com") - gov_website, _ = Website.objects.get_or_create(website="igorville.gov") - domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") - application = DomainApplication.objects.create( - creator=user, - investigator=user, - organization_type=DomainApplication.OrganizationChoices.FEDERAL, - federal_type=DomainApplication.BranchChoices.EXECUTIVE, - is_election_board=False, - organization_name="Test", - address_line1="100 Main St.", - address_line2="APT 1A", - state_territory="CA", - zipcode="12345-6789", - authorizing_official=contact, - requested_domain=domain, - submitter=contact, - purpose="Igorville rules!", - anything_else="All of Igorville loves the dotgov program.", - is_policy_acknowledged=True, - ) - application.current_websites.add(com_website) - application.alternative_domains.add(gov_website) - application.other_contacts.add(contact) - application.save() + with less_console_noise(): + user, _ = User.objects.get_or_create(username="testy") + contact = Contact.objects.create() + com_website, _ = Website.objects.get_or_create(website="igorville.com") + gov_website, _ = Website.objects.get_or_create(website="igorville.gov") + domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=user, + investigator=user, + organization_type=DomainApplication.OrganizationChoices.FEDERAL, + federal_type=DomainApplication.BranchChoices.EXECUTIVE, + is_election_board=False, + organization_name="Test", + address_line1="100 Main St.", + address_line2="APT 1A", + state_territory="CA", + zipcode="12345-6789", + authorizing_official=contact, + requested_domain=domain, + submitter=contact, + purpose="Igorville rules!", + anything_else="All of Igorville loves the dotgov program.", + is_policy_acknowledged=True, + ) + application.current_websites.add(com_website) + application.alternative_domains.add(gov_website) + application.other_contacts.add(contact) + application.save() def test_domain_info(self): """Can create domain info with all fields.""" - user, _ = User.objects.get_or_create(username="testy") - contact = Contact.objects.create() - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - information = DomainInformation.objects.create( - creator=user, - organization_type=DomainInformation.OrganizationChoices.FEDERAL, - federal_type=DomainInformation.BranchChoices.EXECUTIVE, - is_election_board=False, - organization_name="Test", - address_line1="100 Main St.", - address_line2="APT 1A", - state_territory="CA", - zipcode="12345-6789", - authorizing_official=contact, - submitter=contact, - purpose="Igorville rules!", - anything_else="All of Igorville loves the dotgov program.", - is_policy_acknowledged=True, - domain=domain, - ) - information.other_contacts.add(contact) - information.save() - self.assertEqual(information.domain.id, domain.id) - self.assertEqual(information.id, domain.domain_info.id) + with less_console_noise(): + user, _ = User.objects.get_or_create(username="testy") + contact = Contact.objects.create() + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + information = DomainInformation.objects.create( + creator=user, + organization_type=DomainInformation.OrganizationChoices.FEDERAL, + federal_type=DomainInformation.BranchChoices.EXECUTIVE, + is_election_board=False, + organization_name="Test", + address_line1="100 Main St.", + address_line2="APT 1A", + state_territory="CA", + zipcode="12345-6789", + authorizing_official=contact, + submitter=contact, + purpose="Igorville rules!", + anything_else="All of Igorville loves the dotgov program.", + is_policy_acknowledged=True, + domain=domain, + ) + information.other_contacts.add(contact) + information.save() + self.assertEqual(information.domain.id, domain.id) + self.assertEqual(information.id, domain.domain_info.id) def test_status_fsm_submit_fail(self): - user, _ = User.objects.get_or_create(username="testy") - application = DomainApplication.objects.create(creator=user) + with less_console_noise(): + user, _ = User.objects.get_or_create(username="testy") + application = DomainApplication.objects.create(creator=user) - with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - with self.assertRaises(ValueError): - # can't submit an application with a null domain name - application.submit() + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + with less_console_noise(): + with self.assertRaises(ValueError): + # can't submit an application with a null domain name + application.submit() def test_status_fsm_submit_succeed(self): - user, _ = User.objects.get_or_create(username="testy") - site = DraftDomain.objects.create(name="igorville.gov") - application = DomainApplication.objects.create(creator=user, requested_domain=site) + with less_console_noise(): + user, _ = User.objects.get_or_create(username="testy") + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create(creator=user, requested_domain=site) - # no submitter email so this emits a log warning + # no submitter email so this emits a log warning - with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): - application.submit() - self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED) + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + with less_console_noise(): + application.submit() + self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED) def test_submit_sends_email(self): """Create an application and submit it and see if email was sent.""" - user, _ = User.objects.get_or_create(username="testy") - contact = Contact.objects.create(email="test@test.gov") - domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") - application = DomainApplication.objects.create( - creator=user, - requested_domain=domain, - submitter=contact, - ) - application.save() + with less_console_noise(): + user, _ = User.objects.get_or_create(username="testy") + contact = Contact.objects.create(email="test@test.gov") + domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=user, + requested_domain=domain, + submitter=contact, + ) + application.save() - with boto3_mocking.clients.handler_for("sesv2", self.mock_client): - with less_console_noise(): + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): application.submit() - # check to see if an email was sent - self.assertGreater( - len( - [ - email - for email in MockSESClient.EMAILS_SENT - if "test@test.gov" in email["kwargs"]["Destination"]["ToAddresses"] - ] - ), - 0, - ) + # check to see if an email was sent + self.assertGreater( + len( + [ + email + for email in MockSESClient.EMAILS_SENT + if "test@test.gov" in email["kwargs"]["Destination"]["ToAddresses"] + ] + ), + 0, + ) def test_submit_transition_allowed(self): """ @@ -268,13 +275,13 @@ class TestDomainApplication(TestCase): (self.rejected_application, TransitionNotAllowed), (self.ineligible_application, TransitionNotAllowed), ] - - for application, exception_type in test_cases: - with self.subTest(application=application, exception_type=exception_type): - try: - application.action_needed() - except TransitionNotAllowed: - self.fail("TransitionNotAllowed was raised, but it was not expected.") + with less_console_noise(): + for application, exception_type in test_cases: + with self.subTest(application=application, exception_type=exception_type): + try: + application.action_needed() + except TransitionNotAllowed: + self.fail("TransitionNotAllowed was raised, but it was not expected.") def test_action_needed_transition_not_allowed(self): """ @@ -286,11 +293,11 @@ class TestDomainApplication(TestCase): (self.action_needed_application, TransitionNotAllowed), (self.withdrawn_application, TransitionNotAllowed), ] - - for application, exception_type in test_cases: - with self.subTest(application=application, exception_type=exception_type): - with self.assertRaises(exception_type): - application.action_needed() + with less_console_noise(): + for application, exception_type in test_cases: + with self.subTest(application=application, exception_type=exception_type): + with self.assertRaises(exception_type): + application.action_needed() def test_approved_transition_allowed(self): """ @@ -499,25 +506,29 @@ class TestDomainApplication(TestCase): def test_has_rationale_returns_true(self): """has_rationale() returns true when an application has no_other_contacts_rationale""" - self.started_application.no_other_contacts_rationale = "You talkin' to me?" - self.started_application.save() - self.assertEquals(self.started_application.has_rationale(), True) + with less_console_noise(): + self.started_application.no_other_contacts_rationale = "You talkin' to me?" + self.started_application.save() + self.assertEquals(self.started_application.has_rationale(), True) def test_has_rationale_returns_false(self): """has_rationale() returns false when an application has no no_other_contacts_rationale""" - self.assertEquals(self.started_application.has_rationale(), False) + with less_console_noise(): + self.assertEquals(self.started_application.has_rationale(), False) def test_has_other_contacts_returns_true(self): """has_other_contacts() returns true when an application has other_contacts""" - # completed_application has other contacts by default - self.assertEquals(self.started_application.has_other_contacts(), True) + with less_console_noise(): + # completed_application has other contacts by default + self.assertEquals(self.started_application.has_other_contacts(), True) def test_has_other_contacts_returns_false(self): """has_other_contacts() returns false when an application has no other_contacts""" - application = completed_application( - status=DomainApplication.ApplicationStatus.STARTED, name="no-others.gov", has_other_contacts=False - ) - self.assertEquals(application.has_other_contacts(), False) + with less_console_noise(): + application = completed_application( + status=DomainApplication.ApplicationStatus.STARTED, name="no-others.gov", has_other_contacts=False + ) + self.assertEquals(application.has_other_contacts(), False) class TestPermissions(TestCase): @@ -548,9 +559,9 @@ class TestPermissions(TestCase): self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain)) -class TestDomainInfo(TestCase): +class TestDomainInformation(TestCase): - """Test creation of Domain Information when approved.""" + """Test the DomainInformation model, when approved or otherwise""" def setUp(self): super().setUp() @@ -559,12 +570,18 @@ class TestDomainInfo(TestCase): def tearDown(self): super().tearDown() self.mock_client.EMAILS_SENT.clear() + Domain.objects.all().delete() + DomainInformation.objects.all().delete() + DomainApplication.objects.all().delete() + User.objects.all().delete() + DraftDomain.objects.all().delete() @boto3_mocking.patching def test_approval_creates_info(self): + self.maxDiff = None draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") user, _ = User.objects.get_or_create() - application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) + application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain, notes="test notes") with boto3_mocking.clients.handler_for("sesv2", self.mock_client): with less_console_noise(): @@ -574,7 +591,25 @@ class TestDomainInfo(TestCase): # should be an information present for this domain domain = Domain.objects.get(name="igorville.gov") - self.assertTrue(DomainInformation.objects.get(domain=domain)) + domain_information = DomainInformation.objects.filter(domain=domain) + self.assertTrue(domain_information.exists()) + + # Test that both objects are what we expect + current_domain_information = domain_information.get().__dict__ + expected_domain_information = DomainInformation( + creator=user, + domain=domain, + notes="test notes", + domain_application=application, + ).__dict__ + + # Test the two records for consistency + self.assertEqual(self.clean_dict(current_domain_information), self.clean_dict(expected_domain_information)) + + def clean_dict(self, dict_obj): + """Cleans dynamic fields in a dictionary""" + bad_fields = ["_state", "created_at", "id", "updated_at"] + return {k: v for k, v in dict_obj.items() if k not in bad_fields} class TestInvitations(TestCase): diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 9026832cd..ca0a5e8d8 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -7,6 +7,7 @@ from django.test import TestCase from django.db.utils import IntegrityError from unittest.mock import MagicMock, patch, call import datetime +from django.utils.timezone import make_aware from registrar.models import Domain, Host, HostIP from unittest import skip @@ -46,158 +47,162 @@ class TestDomainCache(MockEppLib): def test_cache_sets_resets(self): """Cache should be set on getter and reset on setter calls""" - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - # trigger getter - _ = domain.creation_date - domain._get_property("contacts") - # getter should set the domain cache with a InfoDomain object - # (see InfoDomainResult) - self.assertEquals(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) - self.assertEquals(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) - status_list = [status.state for status in self.mockDataInfoDomain.statuses] - self.assertEquals(domain._cache["statuses"], status_list) - self.assertFalse("avail" in domain._cache.keys()) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + # trigger getter + _ = domain.creation_date + domain._get_property("contacts") + # getter should set the domain cache with a InfoDomain object + # (see InfoDomainResult) + self.assertEquals(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) + self.assertEquals(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) + status_list = [status.state for status in self.mockDataInfoDomain.statuses] + self.assertEquals(domain._cache["statuses"], status_list) + self.assertFalse("avail" in domain._cache.keys()) - # using a setter should clear the cache - domain.dnssecdata = [] - self.assertEquals(domain._cache, {}) + # using a setter should clear the cache + domain.dnssecdata = [] + self.assertEquals(domain._cache, {}) - # send should have been called only once - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoDomain(name="igorville.gov", auth_info=None), - cleaned=True, - ), - ], - any_order=False, # Ensure calls are in the specified order - ) + # send should have been called only once + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="igorville.gov", auth_info=None), + cleaned=True, + ), + ], + any_order=False, # Ensure calls are in the specified order + ) def test_cache_used_when_avail(self): """Cache is pulled from if the object has already been accessed""" - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - cr_date = domain.creation_date + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + cr_date = domain.creation_date - # repeat the getter call - cr_date = domain.creation_date + # repeat the getter call + cr_date = domain.creation_date - # value should still be set correctly - self.assertEqual(cr_date, self.mockDataInfoDomain.cr_date) - self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) + # value should still be set correctly + self.assertEqual(cr_date, self.mockDataInfoDomain.cr_date) + self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) - # send was only called once & not on the second getter call - expectedCalls = [ - call(commands.InfoDomain(name="igorville.gov", auth_info=None), cleaned=True), - ] + # send was only called once & not on the second getter call + expectedCalls = [ + call(commands.InfoDomain(name="igorville.gov", auth_info=None), cleaned=True), + ] - self.mockedSendFunction.assert_has_calls(expectedCalls) + self.mockedSendFunction.assert_has_calls(expectedCalls) def test_cache_nested_elements(self): """Cache works correctly with the nested objects cache and hosts""" - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - # The contact list will initially contain objects of type 'DomainContact' - # this is then transformed into PublicContact, and cache should NOT - # hold onto the DomainContact object - expectedUnfurledContactsList = [ - common.DomainContact(contact="123", type="security"), - ] - expectedContactsDict = { - PublicContact.ContactTypeChoices.ADMINISTRATIVE: None, - PublicContact.ContactTypeChoices.SECURITY: "123", - PublicContact.ContactTypeChoices.TECHNICAL: None, - } - expectedHostsDict = { - "name": self.mockDataInfoDomain.hosts[0], - "addrs": [item.addr for item in self.mockDataInfoHosts.addrs], - "cr_date": self.mockDataInfoHosts.cr_date, - } + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + # The contact list will initially contain objects of type 'DomainContact' + # this is then transformed into PublicContact, and cache should NOT + # hold onto the DomainContact object + expectedUnfurledContactsList = [ + common.DomainContact(contact="123", type="security"), + ] + expectedContactsDict = { + PublicContact.ContactTypeChoices.ADMINISTRATIVE: None, + PublicContact.ContactTypeChoices.SECURITY: "123", + PublicContact.ContactTypeChoices.TECHNICAL: None, + } + expectedHostsDict = { + "name": self.mockDataInfoDomain.hosts[0], + "addrs": [item.addr for item in self.mockDataInfoHosts.addrs], + "cr_date": self.mockDataInfoHosts.cr_date, + } - # this can be changed when the getter for contacts is implemented - domain._get_property("contacts") + # this can be changed when the getter for contacts is implemented + domain._get_property("contacts") - # check domain info is still correct and not overridden - self.assertEqual(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) - self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) + # check domain info is still correct and not overridden + self.assertEqual(domain._cache["auth_info"], self.mockDataInfoDomain.auth_info) + self.assertEqual(domain._cache["cr_date"], self.mockDataInfoDomain.cr_date) - # check contacts - self.assertEqual(domain._cache["_contacts"], self.mockDataInfoDomain.contacts) - # The contact list should not contain what is sent by the registry by default, - # as _fetch_cache will transform the type to PublicContact - self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) - self.assertEqual(domain._cache["contacts"], expectedContactsDict) + # check contacts + self.assertEqual(domain._cache["_contacts"], self.mockDataInfoDomain.contacts) + # The contact list should not contain what is sent by the registry by default, + # as _fetch_cache will transform the type to PublicContact + self.assertNotEqual(domain._cache["contacts"], expectedUnfurledContactsList) + self.assertEqual(domain._cache["contacts"], expectedContactsDict) - # get and check hosts is set correctly - domain._get_property("hosts") - self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - self.assertEqual(domain._cache["contacts"], expectedContactsDict) - # invalidate cache - domain._cache = {} + # get and check hosts is set correctly + domain._get_property("hosts") + self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) + self.assertEqual(domain._cache["contacts"], expectedContactsDict) + # invalidate cache + domain._cache = {} - # get host - domain._get_property("hosts") - self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) + # get host + domain._get_property("hosts") + self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - # get contacts - domain._get_property("contacts") - self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) - self.assertEqual(domain._cache["contacts"], expectedContactsDict) + # get contacts + domain._get_property("contacts") + self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) + self.assertEqual(domain._cache["contacts"], expectedContactsDict) def test_map_epp_contact_to_public_contact(self): # Tests that the mapper is working how we expect - domain, _ = Domain.objects.get_or_create(name="registry.gov") - security = PublicContact.ContactTypeChoices.SECURITY - mapped = domain.map_epp_contact_to_public_contact( - self.mockDataInfoContact, - self.mockDataInfoContact.id, - security, - ) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="registry.gov") + security = PublicContact.ContactTypeChoices.SECURITY + mapped = domain.map_epp_contact_to_public_contact( + self.mockDataInfoContact, + self.mockDataInfoContact.id, + security, + ) - expected_contact = PublicContact( - domain=domain, - contact_type=security, - registry_id="123", - email="123@mail.gov", - voice="+1.8882820870", - fax="+1-212-9876543", - pw="lastPw", - name="Registry Customer Service", - org="Cybersecurity and Infrastructure Security Agency", - city="Arlington", - pc="22201", - cc="US", - sp="VA", - street1="4200 Wilson Blvd.", - ) + expected_contact = PublicContact( + domain=domain, + contact_type=security, + registry_id="123", + email="123@mail.gov", + voice="+1.8882820870", + fax="+1-212-9876543", + pw="lastPw", + name="Registry Customer Service", + org="Cybersecurity and Infrastructure Security Agency", + city="Arlington", + pc="22201", + cc="US", + sp="VA", + street1="4200 Wilson Blvd.", + ) - # Test purposes only, since we're comparing - # two duplicate objects. We would expect - # these not to have the same state. - expected_contact._state = mapped._state + # Test purposes only, since we're comparing + # two duplicate objects. We would expect + # these not to have the same state. + expected_contact._state = mapped._state - # Mapped object is what we expect - self.assertEqual(mapped.__dict__, expected_contact.__dict__) + # Mapped object is what we expect + self.assertEqual(mapped.__dict__, expected_contact.__dict__) - # The mapped object should correctly translate to a DB - # object. If not, something else went wrong. - db_object = domain._get_or_create_public_contact(mapped) - in_db = PublicContact.objects.filter( - registry_id=domain.security_contact.registry_id, - contact_type=security, - ).get() - # DB Object is the same as the mapped object - self.assertEqual(db_object, in_db) + # The mapped object should correctly translate to a DB + # object. If not, something else went wrong. + db_object = domain._get_or_create_public_contact(mapped) + in_db = PublicContact.objects.filter( + registry_id=domain.security_contact.registry_id, + contact_type=security, + ).get() + # DB Object is the same as the mapped object + self.assertEqual(db_object, in_db) - domain.security_contact = in_db - # Trigger the getter - _ = domain.security_contact - # Check to see that changes made - # to DB objects persist in cache correctly - in_db.email = "123test@mail.gov" - in_db.save() + domain.security_contact = in_db + # Trigger the getter + _ = domain.security_contact + # Check to see that changes made + # to DB objects persist in cache correctly + in_db.email = "123test@mail.gov" + in_db.save() - cached_contact = domain._cache["contacts"].get(security) - self.assertEqual(cached_contact, in_db.registry_id) - self.assertEqual(domain.security_contact.email, "123test@mail.gov") + cached_contact = domain._cache["contacts"].get(security) + self.assertEqual(cached_contact, in_db.registry_id) + self.assertEqual(domain.security_contact.email, "123test@mail.gov") def test_errors_map_epp_contact_to_public_contact(self): """ @@ -206,48 +211,49 @@ class TestDomainCache(MockEppLib): gets invalid data from EPPLib Then the function throws the expected ContactErrors """ - domain, _ = Domain.objects.get_or_create(name="registry.gov") - fakedEpp = self.fakedEppObject() - invalid_length = fakedEpp.dummyInfoContactResultData( - "Cymaticsisasubsetofmodalvibrationalphenomena", "lengthInvalid@mail.gov" - ) - valid_object = fakedEpp.dummyInfoContactResultData("valid", "valid@mail.gov") - - desired_error = ContactErrorCodes.CONTACT_ID_INVALID_LENGTH - with self.assertRaises(ContactError) as context: - domain.map_epp_contact_to_public_contact( - invalid_length, - invalid_length.id, - PublicContact.ContactTypeChoices.SECURITY, + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="registry.gov") + fakedEpp = self.fakedEppObject() + invalid_length = fakedEpp.dummyInfoContactResultData( + "Cymaticsisasubsetofmodalvibrationalphenomena", "lengthInvalid@mail.gov" ) - self.assertEqual(context.exception.code, desired_error) + valid_object = fakedEpp.dummyInfoContactResultData("valid", "valid@mail.gov") - desired_error = ContactErrorCodes.CONTACT_ID_NONE - with self.assertRaises(ContactError) as context: - domain.map_epp_contact_to_public_contact( - valid_object, - None, - PublicContact.ContactTypeChoices.SECURITY, - ) - self.assertEqual(context.exception.code, desired_error) + desired_error = ContactErrorCodes.CONTACT_ID_INVALID_LENGTH + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + invalid_length, + invalid_length.id, + PublicContact.ContactTypeChoices.SECURITY, + ) + self.assertEqual(context.exception.code, desired_error) - desired_error = ContactErrorCodes.CONTACT_INVALID_TYPE - with self.assertRaises(ContactError) as context: - domain.map_epp_contact_to_public_contact( - "bad_object", - valid_object.id, - PublicContact.ContactTypeChoices.SECURITY, - ) - self.assertEqual(context.exception.code, desired_error) + desired_error = ContactErrorCodes.CONTACT_ID_NONE + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + valid_object, + None, + PublicContact.ContactTypeChoices.SECURITY, + ) + self.assertEqual(context.exception.code, desired_error) - desired_error = ContactErrorCodes.CONTACT_TYPE_NONE - with self.assertRaises(ContactError) as context: - domain.map_epp_contact_to_public_contact( - valid_object, - valid_object.id, - None, - ) - self.assertEqual(context.exception.code, desired_error) + desired_error = ContactErrorCodes.CONTACT_INVALID_TYPE + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + "bad_object", + valid_object.id, + PublicContact.ContactTypeChoices.SECURITY, + ) + self.assertEqual(context.exception.code, desired_error) + + desired_error = ContactErrorCodes.CONTACT_TYPE_NONE + with self.assertRaises(ContactError) as context: + domain.map_epp_contact_to_public_contact( + valid_object, + valid_object.id, + None, + ) + self.assertEqual(context.exception.code, desired_error) class TestDomainCreation(MockEppLib): @@ -346,42 +352,44 @@ class TestDomainStatuses(MockEppLib): def test_get_status(self): """Domain 'statuses' getter returns statuses by calling epp""" - domain, _ = Domain.objects.get_or_create(name="chicken-liver.gov") - # trigger getter - _ = domain.statuses - status_list = [status.state for status in self.mockDataInfoDomain.statuses] - self.assertEquals(domain._cache["statuses"], status_list) - # Called in _fetch_cache - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoDomain(name="chicken-liver.gov", auth_info=None), - cleaned=True, - ), - ], - any_order=False, # Ensure calls are in the specified order - ) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="chicken-liver.gov") + # trigger getter + _ = domain.statuses + status_list = [status.state for status in self.mockDataInfoDomain.statuses] + self.assertEquals(domain._cache["statuses"], status_list) + # Called in _fetch_cache + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoDomain(name="chicken-liver.gov", auth_info=None), + cleaned=True, + ), + ], + any_order=False, # Ensure calls are in the specified order + ) def test_get_status_returns_empty_list_when_value_error(self): """Domain 'statuses' getter returns an empty list when value error""" - domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov") + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov") - def side_effect(self): - raise KeyError + def side_effect(self): + raise KeyError - patcher = patch("registrar.models.domain.Domain._get_property") - mocked_get = patcher.start() - mocked_get.side_effect = side_effect + patcher = patch("registrar.models.domain.Domain._get_property") + mocked_get = patcher.start() + mocked_get.side_effect = side_effect - # trigger getter - _ = domain.statuses + # trigger getter + _ = domain.statuses - with self.assertRaises(KeyError): - _ = domain._cache["statuses"] - self.assertEquals(_, []) + with self.assertRaises(KeyError): + _ = domain._cache["statuses"] + self.assertEquals(_, []) - patcher.stop() + patcher.stop() @skip("not implemented yet") def test_place_client_hold_sets_status(self): @@ -398,28 +406,23 @@ class TestDomainStatuses(MockEppLib): first_ready is set when a domain is first transitioned to READY. It does not get overwritten in case the domain gets out of and back into READY. """ - domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov", state=Domain.State.DNS_NEEDED) - self.assertEqual(domain.first_ready, None) - - domain.ready() - - # check that status is READY - self.assertTrue(domain.is_active()) - self.assertNotEqual(domain.first_ready, None) - - # Capture the value of first_ready - first_ready = domain.first_ready - - # change domain status - domain.dns_needed() - self.assertFalse(domain.is_active()) - - # change back to READY - domain.ready() - self.assertTrue(domain.is_active()) - - # assert that the value of first_ready has not changed - self.assertEqual(domain.first_ready, first_ready) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov", state=Domain.State.DNS_NEEDED) + self.assertEqual(domain.first_ready, None) + domain.ready() + # check that status is READY + self.assertTrue(domain.is_active()) + self.assertNotEqual(domain.first_ready, None) + # Capture the value of first_ready + first_ready = domain.first_ready + # change domain status + domain.dns_needed() + self.assertFalse(domain.is_active()) + # change back to READY + domain.ready() + self.assertTrue(domain.is_active()) + # assert that the value of first_ready has not changed + self.assertEqual(domain.first_ready, first_ready) def tearDown(self) -> None: PublicContact.objects.all().delete() @@ -557,37 +560,32 @@ class TestRegistrantContacts(MockEppLib): Then the domain has a valid security contact with CISA defaults And disclose flags are set to keep the email address hidden """ - - # making a domain should make it domain - expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = self.domain - - self.domain.dns_needed_from_unknown() - - self.assertEqual(self.mockedSendFunction.call_count, 8) - self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 4) - self.assertEqual( - PublicContact.objects.get( + with less_console_noise(): + # making a domain should make it domain + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = self.domain + self.domain.dns_needed_from_unknown() + self.assertEqual(self.mockedSendFunction.call_count, 8) + self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 4) + self.assertEqual( + PublicContact.objects.get( + domain=self.domain, + contact_type=PublicContact.ContactTypeChoices.SECURITY, + ).email, + expectedSecContact.email, + ) + id = PublicContact.objects.get( domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY, - ).email, - expectedSecContact.email, - ) - - id = PublicContact.objects.get( - domain=self.domain, - contact_type=PublicContact.ContactTypeChoices.SECURITY, - ).registry_id - - expectedSecContact.registry_id = id - expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False) - expectedUpdateDomain = commands.UpdateDomain( - name=self.domain.name, - add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")], - ) - - self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True) + ).registry_id + expectedSecContact.registry_id = id + expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False) + expectedUpdateDomain = commands.UpdateDomain( + name=self.domain.name, + add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")], + ) + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True) def test_user_adds_security_email(self): """ @@ -598,35 +596,31 @@ class TestRegistrantContacts(MockEppLib): And Domain sends `commands.UpdateDomain` to the registry with the newly created contact of type 'security' """ - # make a security contact that is a PublicContact - # make sure a security email already exists - self.domain.dns_needed_from_unknown() - expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = self.domain - expectedSecContact.email = "newEmail@fake.com" - expectedSecContact.registry_id = "456" - expectedSecContact.name = "Fakey McFakerson" - - # calls the security contact setter as if you did - # self.domain.security_contact=expectedSecContact - expectedSecContact.save() - - # no longer the default email it should be disclosed - expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True) - - expectedUpdateDomain = commands.UpdateDomain( - name=self.domain.name, - add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")], - ) - - # check that send has triggered the create command for the contact - receivedSecurityContact = PublicContact.objects.get( - domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY - ) - - self.assertEqual(receivedSecurityContact, expectedSecContact) - self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True) + with less_console_noise(): + # make a security contact that is a PublicContact + # make sure a security email already exists + self.domain.dns_needed_from_unknown() + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = self.domain + expectedSecContact.email = "newEmail@fake.com" + expectedSecContact.registry_id = "456" + expectedSecContact.name = "Fakey McFakerson" + # calls the security contact setter as if you did + # self.domain.security_contact=expectedSecContact + expectedSecContact.save() + # no longer the default email it should be disclosed + expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True) + expectedUpdateDomain = commands.UpdateDomain( + name=self.domain.name, + add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")], + ) + # check that send has triggered the create command for the contact + receivedSecurityContact = PublicContact.objects.get( + domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY + ) + self.assertEqual(receivedSecurityContact, expectedSecContact) + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True) def test_security_email_is_idempotent(self): """ @@ -635,26 +629,23 @@ class TestRegistrantContacts(MockEppLib): to the registry twice with identical data Then no errors are raised in Domain """ - - security_contact = self.domain.get_default_security_contact() - security_contact.registry_id = "fail" - security_contact.save() - - self.domain.security_contact = security_contact - - expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=False) - - expectedUpdateDomain = commands.UpdateDomain( - name=self.domain.name, - add=[common.DomainContact(contact=security_contact.registry_id, type="security")], - ) - expected_calls = [ - call(expectedCreateCommand, cleaned=True), - call(expectedCreateCommand, cleaned=True), - call(expectedUpdateDomain, cleaned=True), - ] - self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) - self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) + with less_console_noise(): + security_contact = self.domain.get_default_security_contact() + security_contact.registry_id = "fail" + security_contact.save() + self.domain.security_contact = security_contact + expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=False) + expectedUpdateDomain = commands.UpdateDomain( + name=self.domain.name, + add=[common.DomainContact(contact=security_contact.registry_id, type="security")], + ) + expected_calls = [ + call(expectedCreateCommand, cleaned=True), + call(expectedCreateCommand, cleaned=True), + call(expectedUpdateDomain, cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) + self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) def test_user_deletes_security_email(self): """ @@ -667,51 +658,47 @@ class TestRegistrantContacts(MockEppLib): And the domain has a valid security contact with CISA defaults And disclose flags are set to keep the email address hidden """ - old_contact = self.domain.get_default_security_contact() - - old_contact.registry_id = "fail" - old_contact.email = "user.entered@email.com" - old_contact.save() - new_contact = self.domain.get_default_security_contact() - new_contact.registry_id = "fail" - new_contact.email = "" - self.domain.security_contact = new_contact - - firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose_email=True) - updateDomainAddCall = commands.UpdateDomain( - name=self.domain.name, - add=[common.DomainContact(contact=old_contact.registry_id, type="security")], - ) - self.assertEqual( - PublicContact.objects.filter(domain=self.domain).get().email, - PublicContact.get_default_security().email, - ) - # this one triggers the fail - secondCreateContact = self._convertPublicContactToEpp(new_contact, disclose_email=True) - updateDomainRemCall = commands.UpdateDomain( - name=self.domain.name, - rem=[common.DomainContact(contact=old_contact.registry_id, type="security")], - ) - - defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id - default_security = PublicContact.get_default_security() - default_security.registry_id = defaultSecID - createDefaultContact = self._convertPublicContactToEpp(default_security, disclose_email=False) - updateDomainWDefault = commands.UpdateDomain( - name=self.domain.name, - add=[common.DomainContact(contact=defaultSecID, type="security")], - ) - - expected_calls = [ - call(firstCreateContactCall, cleaned=True), - call(updateDomainAddCall, cleaned=True), - call(secondCreateContact, cleaned=True), - call(updateDomainRemCall, cleaned=True), - call(createDefaultContact, cleaned=True), - call(updateDomainWDefault, cleaned=True), - ] - - self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) + with less_console_noise(): + old_contact = self.domain.get_default_security_contact() + old_contact.registry_id = "fail" + old_contact.email = "user.entered@email.com" + old_contact.save() + new_contact = self.domain.get_default_security_contact() + new_contact.registry_id = "fail" + new_contact.email = "" + self.domain.security_contact = new_contact + firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose_email=True) + updateDomainAddCall = commands.UpdateDomain( + name=self.domain.name, + add=[common.DomainContact(contact=old_contact.registry_id, type="security")], + ) + self.assertEqual( + PublicContact.objects.filter(domain=self.domain).get().email, + PublicContact.get_default_security().email, + ) + # this one triggers the fail + secondCreateContact = self._convertPublicContactToEpp(new_contact, disclose_email=True) + updateDomainRemCall = commands.UpdateDomain( + name=self.domain.name, + rem=[common.DomainContact(contact=old_contact.registry_id, type="security")], + ) + defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id + default_security = PublicContact.get_default_security() + default_security.registry_id = defaultSecID + createDefaultContact = self._convertPublicContactToEpp(default_security, disclose_email=False) + updateDomainWDefault = commands.UpdateDomain( + name=self.domain.name, + add=[common.DomainContact(contact=defaultSecID, type="security")], + ) + expected_calls = [ + call(firstCreateContactCall, cleaned=True), + call(updateDomainAddCall, cleaned=True), + call(secondCreateContact, cleaned=True), + call(updateDomainRemCall, cleaned=True), + call(createDefaultContact, cleaned=True), + call(updateDomainWDefault, cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) def test_updates_security_email(self): """ @@ -721,29 +708,28 @@ class TestRegistrantContacts(MockEppLib): security contact email Then Domain sends `commands.UpdateContact` to the registry """ - security_contact = self.domain.get_default_security_contact() - security_contact.email = "originalUserEmail@gmail.com" - security_contact.registry_id = "fail" - security_contact.save() - expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True) - - expectedUpdateDomain = commands.UpdateDomain( - name=self.domain.name, - add=[common.DomainContact(contact=security_contact.registry_id, type="security")], - ) - security_contact.email = "changedEmail@email.com" - security_contact.save() - expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True) - updateContact = self._convertPublicContactToEpp(security_contact, disclose_email=True, createContact=False) - - expected_calls = [ - call(expectedCreateCommand, cleaned=True), - call(expectedUpdateDomain, cleaned=True), - call(expectedSecondCreateCommand, cleaned=True), - call(updateContact, cleaned=True), - ] - self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) - self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) + with less_console_noise(): + security_contact = self.domain.get_default_security_contact() + security_contact.email = "originalUserEmail@gmail.com" + security_contact.registry_id = "fail" + security_contact.save() + expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True) + expectedUpdateDomain = commands.UpdateDomain( + name=self.domain.name, + add=[common.DomainContact(contact=security_contact.registry_id, type="security")], + ) + security_contact.email = "changedEmail@email.com" + security_contact.save() + expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True) + updateContact = self._convertPublicContactToEpp(security_contact, disclose_email=True, createContact=False) + expected_calls = [ + call(expectedCreateCommand, cleaned=True), + call(expectedUpdateDomain, cleaned=True), + call(expectedSecondCreateCommand, cleaned=True), + call(updateContact, cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) + self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) def test_security_email_returns_on_registry_error(self): """ @@ -751,28 +737,26 @@ class TestRegistrantContacts(MockEppLib): Registry is unavailable and throws exception when attempting to build cache from registry. Security email retrieved from database. """ - # Use self.domain_contact which has been initialized with existing contacts, including securityContact + with less_console_noise(): + # Use self.domain_contact which has been initialized with existing contacts, including securityContact + # call get_security_email to initially set the security_contact_registry_id in the domain model + self.domain_contact.get_security_email() + # invalidate the cache so the next time get_security_email is called, it has to attempt to populate cache + self.domain_contact._invalidate_cache() - # call get_security_email to initially set the security_contact_registry_id in the domain model - self.domain_contact.get_security_email() - # invalidate the cache so the next time get_security_email is called, it has to attempt to populate cache - self.domain_contact._invalidate_cache() + # mock that registry throws an error on the EPP send + def side_effect(_request, cleaned): + raise RegistryError(code=ErrorCode.COMMAND_FAILED) - # mock that registry throws an error on the EPP send - def side_effect(_request, cleaned): - raise RegistryError(code=ErrorCode.COMMAND_FAILED) - - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - # when get_security_email is called, the registry error will force the security contact - # to be retrieved using the security_contact_registry_id in the domain model - security_email = self.domain_contact.get_security_email() - - # assert that the proper security contact was retrieved by testing the email matches expected value - self.assertEqual(security_email, "security@mail.gov") - patcher.stop() + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + # when get_security_email is called, the registry error will force the security contact + # to be retrieved using the security_contact_registry_id in the domain model + security_email = self.domain_contact.get_security_email() + # assert that the proper security contact was retrieved by testing the email matches expected value + self.assertEqual(security_email, "security@mail.gov") + patcher.stop() def test_security_email_stored_on_fetch_cache(self): """ @@ -781,13 +765,14 @@ class TestRegistrantContacts(MockEppLib): The mocked data for the EPP calls for the freeman.gov domain returns a security contact with registry id of securityContact when InfoContact is called """ - # Use self.domain_contact which has been initialized with existing contacts, including securityContact + with less_console_noise(): + # Use self.domain_contact which has been initialized with existing contacts, including securityContact - # force fetch_cache to be called, which will return above documented mocked hosts - self.domain_contact.get_security_email() + # force fetch_cache to be called, which will return above documented mocked hosts + self.domain_contact.get_security_email() - # assert that the security_contact_registry_id in the db matches "securityContact" - self.assertEqual(self.domain_contact.security_contact_registry_id, "securityContact") + # assert that the security_contact_registry_id in the db matches "securityContact" + self.assertEqual(self.domain_contact.security_contact_registry_id, "securityContact") def test_not_disclosed_on_other_contacts(self): """ @@ -798,113 +783,101 @@ class TestRegistrantContacts(MockEppLib): And the field `disclose` is set to false for DF.EMAIL on all fields except security """ - # Generates a domain with four existing contacts - domain, _ = Domain.objects.get_or_create(name="freeman.gov") - - # Contact setup - expected_admin = domain.get_default_administrative_contact() - expected_admin.email = self.mockAdministrativeContact.email - - expected_registrant = domain.get_default_registrant_contact() - expected_registrant.email = self.mockRegistrantContact.email - - expected_security = domain.get_default_security_contact() - expected_security.email = self.mockSecurityContact.email - - expected_tech = domain.get_default_technical_contact() - expected_tech.email = self.mockTechnicalContact.email - - domain.administrative_contact = expected_admin - domain.registrant_contact = expected_registrant - domain.security_contact = expected_security - domain.technical_contact = expected_tech - - contacts = [ - (expected_admin, domain.administrative_contact), - (expected_registrant, domain.registrant_contact), - (expected_security, domain.security_contact), - (expected_tech, domain.technical_contact), - ] - - # Test for each contact - for contact in contacts: - expected_contact = contact[0] - actual_contact = contact[1] - is_security = expected_contact.contact_type == "security" - - expectedCreateCommand = self._convertPublicContactToEpp(expected_contact, disclose_email=is_security) - - # Should only be disclosed if the type is security, as the email is valid - self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - - # The emails should match on both items - self.assertEqual(expected_contact.email, actual_contact.email) + with less_console_noise(): + # Generates a domain with four existing contacts + domain, _ = Domain.objects.get_or_create(name="freeman.gov") + # Contact setup + expected_admin = domain.get_default_administrative_contact() + expected_admin.email = self.mockAdministrativeContact.email + expected_registrant = domain.get_default_registrant_contact() + expected_registrant.email = self.mockRegistrantContact.email + expected_security = domain.get_default_security_contact() + expected_security.email = self.mockSecurityContact.email + expected_tech = domain.get_default_technical_contact() + expected_tech.email = self.mockTechnicalContact.email + domain.administrative_contact = expected_admin + domain.registrant_contact = expected_registrant + domain.security_contact = expected_security + domain.technical_contact = expected_tech + contacts = [ + (expected_admin, domain.administrative_contact), + (expected_registrant, domain.registrant_contact), + (expected_security, domain.security_contact), + (expected_tech, domain.technical_contact), + ] + # Test for each contact + for contact in contacts: + expected_contact = contact[0] + actual_contact = contact[1] + is_security = expected_contact.contact_type == "security" + expectedCreateCommand = self._convertPublicContactToEpp(expected_contact, disclose_email=is_security) + # Should only be disclosed if the type is security, as the email is valid + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # The emails should match on both items + self.assertEqual(expected_contact.email, actual_contact.email) def test_convert_public_contact_to_epp(self): - domain, _ = Domain.objects.get_or_create(name="freeman.gov") - dummy_contact = domain.get_default_security_contact() - test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__ - test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=False).__dict__ - - # Separated for linter - disclose_email_field = {common.DiscloseField.EMAIL} - expected_disclose = { - "auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"), - "disclose": common.Disclose(flag=True, fields=disclose_email_field, types=None), - "email": "dotgov@cisa.dhs.gov", - "extensions": [], - "fax": None, - "id": "ThIq2NcRIDN7PauO", - "ident": None, - "notify_email": None, - "postal_info": common.PostalInfo( - name="Registry Customer Service", - addr=common.ContactAddr( - street=["4200 Wilson Blvd.", None, None], - city="Arlington", - pc="22201", - cc="US", - sp="VA", + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="freeman.gov") + dummy_contact = domain.get_default_security_contact() + test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__ + test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=False).__dict__ + # Separated for linter + disclose_email_field = {common.DiscloseField.EMAIL} + expected_disclose = { + "auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + "disclose": common.Disclose(flag=True, fields=disclose_email_field, types=None), + "email": "dotgov@cisa.dhs.gov", + "extensions": [], + "fax": None, + "id": "ThIq2NcRIDN7PauO", + "ident": None, + "notify_email": None, + "postal_info": common.PostalInfo( + name="Registry Customer Service", + addr=common.ContactAddr( + street=["4200 Wilson Blvd.", None, None], + city="Arlington", + pc="22201", + cc="US", + sp="VA", + ), + org="Cybersecurity and Infrastructure Security Agency", + type="loc", ), - org="Cybersecurity and Infrastructure Security Agency", - type="loc", - ), - "vat": None, - "voice": "+1.8882820870", - } - - # Separated for linter - expected_not_disclose = { - "auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"), - "disclose": common.Disclose(flag=False, fields=disclose_email_field, types=None), - "email": "dotgov@cisa.dhs.gov", - "extensions": [], - "fax": None, - "id": "ThrECENCHI76PGLh", - "ident": None, - "notify_email": None, - "postal_info": common.PostalInfo( - name="Registry Customer Service", - addr=common.ContactAddr( - street=["4200 Wilson Blvd.", None, None], - city="Arlington", - pc="22201", - cc="US", - sp="VA", + "vat": None, + "voice": "+1.8882820870", + } + # Separated for linter + expected_not_disclose = { + "auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + "disclose": common.Disclose(flag=False, fields=disclose_email_field, types=None), + "email": "dotgov@cisa.dhs.gov", + "extensions": [], + "fax": None, + "id": "ThrECENCHI76PGLh", + "ident": None, + "notify_email": None, + "postal_info": common.PostalInfo( + name="Registry Customer Service", + addr=common.ContactAddr( + street=["4200 Wilson Blvd.", None, None], + city="Arlington", + pc="22201", + cc="US", + sp="VA", + ), + org="Cybersecurity and Infrastructure Security Agency", + type="loc", ), - org="Cybersecurity and Infrastructure Security Agency", - type="loc", - ), - "vat": None, - "voice": "+1.8882820870", - } - - # Set the ids equal, since this value changes - test_disclose["id"] = expected_disclose["id"] - test_not_disclose["id"] = expected_not_disclose["id"] - - self.assertEqual(test_disclose, expected_disclose) - self.assertEqual(test_not_disclose, expected_not_disclose) + "vat": None, + "voice": "+1.8882820870", + } + # Set the ids equal, since this value changes + test_disclose["id"] = expected_disclose["id"] + test_not_disclose["id"] = expected_not_disclose["id"] + self.assertEqual(test_disclose, expected_disclose) + self.assertEqual(test_not_disclose, expected_not_disclose) def test_not_disclosed_on_default_security_contact(self): """ @@ -913,17 +886,16 @@ class TestRegistrantContacts(MockEppLib): Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to false for DF.EMAIL """ - domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov") - expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = domain - expectedSecContact.registry_id = "defaultSec" - domain.security_contact = expectedSecContact - - expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False) - - self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - # Confirm that we are getting a default email - self.assertEqual(domain.security_contact.email, expectedSecContact.email) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov") + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = domain + expectedSecContact.registry_id = "defaultSec" + domain.security_contact = expectedSecContact + expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False) + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # Confirm that we are getting a default email + self.assertEqual(domain.security_contact.email, expectedSecContact.email) def test_not_disclosed_on_default_technical_contact(self): """ @@ -932,17 +904,16 @@ class TestRegistrantContacts(MockEppLib): Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to false for DF.EMAIL """ - domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov") - expectedTechContact = PublicContact.get_default_technical() - expectedTechContact.domain = domain - expectedTechContact.registry_id = "defaultTech" - domain.technical_contact = expectedTechContact - - expectedCreateCommand = self._convertPublicContactToEpp(expectedTechContact, disclose_email=False) - - self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - # Confirm that we are getting a default email - self.assertEqual(domain.technical_contact.email, expectedTechContact.email) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov") + expectedTechContact = PublicContact.get_default_technical() + expectedTechContact.domain = domain + expectedTechContact.registry_id = "defaultTech" + domain.technical_contact = expectedTechContact + expectedCreateCommand = self._convertPublicContactToEpp(expectedTechContact, disclose_email=False) + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # Confirm that we are getting a default email + self.assertEqual(domain.technical_contact.email, expectedTechContact.email) def test_is_disclosed_on_security_contact(self): """ @@ -952,17 +923,16 @@ class TestRegistrantContacts(MockEppLib): Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to true for DF.EMAIL """ - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = domain - expectedSecContact.email = "123@mail.gov" - domain.security_contact = expectedSecContact - - expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True) - - self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - # Confirm that we are getting the desired email - self.assertEqual(domain.security_contact.email, expectedSecContact.email) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = domain + expectedSecContact.email = "123@mail.gov" + domain.security_contact = expectedSecContact + expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True) + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # Confirm that we are getting the desired email + self.assertEqual(domain.security_contact.email, expectedSecContact.email) @skip("not implemented yet") def test_update_is_unsuccessful(self): @@ -974,121 +944,112 @@ class TestRegistrantContacts(MockEppLib): raise def test_contact_getter_security(self): - security = PublicContact.ContactTypeChoices.SECURITY - # Create prexisting object - expected_contact = self.domain.map_epp_contact_to_public_contact( - self.mockSecurityContact, - contact_id="securityContact", - contact_type=security, - ) - - # Checks if we grabbed the correct PublicContact - self.assertEqual(self.domain_contact.security_contact.email, expected_contact.email) - - expected_contact_db = PublicContact.objects.filter( - registry_id=self.domain_contact.security_contact.registry_id, - contact_type=security, - ).get() - - self.assertEqual(self.domain_contact.security_contact, expected_contact_db) - - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoContact(id="securityContact", auth_info=None), - cleaned=True, - ), - ] - ) - # Checks if we are receiving the cache we expect - cache = self.domain_contact._cache["contacts"] - self.assertEqual(cache.get(security), "securityContact") + with less_console_noise(): + security = PublicContact.ContactTypeChoices.SECURITY + # Create prexisting object + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockSecurityContact, + contact_id="securityContact", + contact_type=security, + ) + # Checks if we grabbed the correct PublicContact + self.assertEqual(self.domain_contact.security_contact.email, expected_contact.email) + expected_contact_db = PublicContact.objects.filter( + registry_id=self.domain_contact.security_contact.registry_id, + contact_type=security, + ).get() + self.assertEqual(self.domain_contact.security_contact, expected_contact_db) + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoContact(id="securityContact", auth_info=None), + cleaned=True, + ), + ] + ) + # Checks if we are receiving the cache we expect + cache = self.domain_contact._cache["contacts"] + self.assertEqual(cache.get(security), "securityContact") def test_contact_getter_technical(self): - technical = PublicContact.ContactTypeChoices.TECHNICAL - expected_contact = self.domain.map_epp_contact_to_public_contact( - self.mockTechnicalContact, - contact_id="technicalContact", - contact_type=technical, - ) - - self.assertEqual(self.domain_contact.technical_contact.email, expected_contact.email) - - # Checks if we grab the correct PublicContact - expected_contact_db = PublicContact.objects.filter( - registry_id=self.domain_contact.technical_contact.registry_id, - contact_type=technical, - ).get() - - # Checks if we grab the correct PublicContact - self.assertEqual(self.domain_contact.technical_contact, expected_contact_db) - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoContact(id="technicalContact", auth_info=None), - cleaned=True, - ), - ] - ) - # Checks if we are receiving the cache we expect - cache = self.domain_contact._cache["contacts"] - self.assertEqual(cache.get(technical), "technicalContact") + with less_console_noise(): + technical = PublicContact.ContactTypeChoices.TECHNICAL + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockTechnicalContact, + contact_id="technicalContact", + contact_type=technical, + ) + self.assertEqual(self.domain_contact.technical_contact.email, expected_contact.email) + # Checks if we grab the correct PublicContact + expected_contact_db = PublicContact.objects.filter( + registry_id=self.domain_contact.technical_contact.registry_id, + contact_type=technical, + ).get() + # Checks if we grab the correct PublicContact + self.assertEqual(self.domain_contact.technical_contact, expected_contact_db) + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoContact(id="technicalContact", auth_info=None), + cleaned=True, + ), + ] + ) + # Checks if we are receiving the cache we expect + cache = self.domain_contact._cache["contacts"] + self.assertEqual(cache.get(technical), "technicalContact") def test_contact_getter_administrative(self): - administrative = PublicContact.ContactTypeChoices.ADMINISTRATIVE - expected_contact = self.domain.map_epp_contact_to_public_contact( - self.mockAdministrativeContact, - contact_id="adminContact", - contact_type=administrative, - ) - - self.assertEqual(self.domain_contact.administrative_contact.email, expected_contact.email) - - expected_contact_db = PublicContact.objects.filter( - registry_id=self.domain_contact.administrative_contact.registry_id, - contact_type=administrative, - ).get() - - # Checks if we grab the correct PublicContact - self.assertEqual(self.domain_contact.administrative_contact, expected_contact_db) - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoContact(id="adminContact", auth_info=None), - cleaned=True, - ), - ] - ) - # Checks if we are receiving the cache we expect - cache = self.domain_contact._cache["contacts"] - self.assertEqual(cache.get(administrative), "adminContact") + with less_console_noise(): + administrative = PublicContact.ContactTypeChoices.ADMINISTRATIVE + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockAdministrativeContact, + contact_id="adminContact", + contact_type=administrative, + ) + self.assertEqual(self.domain_contact.administrative_contact.email, expected_contact.email) + expected_contact_db = PublicContact.objects.filter( + registry_id=self.domain_contact.administrative_contact.registry_id, + contact_type=administrative, + ).get() + # Checks if we grab the correct PublicContact + self.assertEqual(self.domain_contact.administrative_contact, expected_contact_db) + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoContact(id="adminContact", auth_info=None), + cleaned=True, + ), + ] + ) + # Checks if we are receiving the cache we expect + cache = self.domain_contact._cache["contacts"] + self.assertEqual(cache.get(administrative), "adminContact") def test_contact_getter_registrant(self): - expected_contact = self.domain.map_epp_contact_to_public_contact( - self.mockRegistrantContact, - contact_id="regContact", - contact_type=PublicContact.ContactTypeChoices.REGISTRANT, - ) - - self.assertEqual(self.domain_contact.registrant_contact.email, expected_contact.email) - - expected_contact_db = PublicContact.objects.filter( - registry_id=self.domain_contact.registrant_contact.registry_id, - contact_type=PublicContact.ContactTypeChoices.REGISTRANT, - ).get() - - # Checks if we grab the correct PublicContact - self.assertEqual(self.domain_contact.registrant_contact, expected_contact_db) - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.InfoContact(id="regContact", auth_info=None), - cleaned=True, - ), - ] - ) - # Checks if we are receiving the cache we expect. - self.assertEqual(self.domain_contact._cache["registrant"], expected_contact_db) + with less_console_noise(): + expected_contact = self.domain.map_epp_contact_to_public_contact( + self.mockRegistrantContact, + contact_id="regContact", + contact_type=PublicContact.ContactTypeChoices.REGISTRANT, + ) + self.assertEqual(self.domain_contact.registrant_contact.email, expected_contact.email) + expected_contact_db = PublicContact.objects.filter( + registry_id=self.domain_contact.registrant_contact.registry_id, + contact_type=PublicContact.ContactTypeChoices.REGISTRANT, + ).get() + # Checks if we grab the correct PublicContact + self.assertEqual(self.domain_contact.registrant_contact, expected_contact_db) + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.InfoContact(id="regContact", auth_info=None), + cleaned=True, + ), + ] + ) + # Checks if we are receiving the cache we expect. + self.assertEqual(self.domain_contact._cache["registrant"], expected_contact_db) class TestRegistrantNameservers(MockEppLib): @@ -1112,76 +1073,78 @@ class TestRegistrantNameservers(MockEppLib): def test_get_nameserver_changes_success_deleted_vals(self): """Testing only deleting and no other changes""" - self.domain._cache["hosts"] = [ - {"name": "ns1.example.com", "addrs": None}, - {"name": "ns2.example.com", "addrs": ["1.2.3.4"]}, - ] - newChanges = [ - ("ns1.example.com",), - ] - ( - deleted_values, - updated_values, - new_values, - oldNameservers, - ) = self.domain.getNameserverChanges(newChanges) + with less_console_noise(): + self.domain._cache["hosts"] = [ + {"name": "ns1.example.com", "addrs": None}, + {"name": "ns2.example.com", "addrs": ["1.2.3.4"]}, + ] + newChanges = [ + ("ns1.example.com",), + ] + ( + deleted_values, + updated_values, + new_values, + oldNameservers, + ) = self.domain.getNameserverChanges(newChanges) - self.assertEqual(deleted_values, ["ns2.example.com"]) - self.assertEqual(updated_values, []) - self.assertEqual(new_values, {}) - self.assertEqual( - oldNameservers, - {"ns1.example.com": None, "ns2.example.com": ["1.2.3.4"]}, - ) + self.assertEqual(deleted_values, ["ns2.example.com"]) + self.assertEqual(updated_values, []) + self.assertEqual(new_values, {}) + self.assertEqual( + oldNameservers, + {"ns1.example.com": None, "ns2.example.com": ["1.2.3.4"]}, + ) def test_get_nameserver_changes_success_updated_vals(self): """Testing only updating no other changes""" - self.domain._cache["hosts"] = [ - {"name": "ns3.my-nameserver.gov", "addrs": ["1.2.3.4"]}, - ] - newChanges = [ - ("ns3.my-nameserver.gov", ["1.2.4.5"]), - ] - ( - deleted_values, - updated_values, - new_values, - oldNameservers, - ) = self.domain.getNameserverChanges(newChanges) - - self.assertEqual(deleted_values, []) - self.assertEqual(updated_values, [("ns3.my-nameserver.gov", ["1.2.4.5"])]) - self.assertEqual(new_values, {}) - self.assertEqual( - oldNameservers, - {"ns3.my-nameserver.gov": ["1.2.3.4"]}, - ) + with less_console_noise(): + self.domain._cache["hosts"] = [ + {"name": "ns3.my-nameserver.gov", "addrs": ["1.2.3.4"]}, + ] + newChanges = [ + ("ns3.my-nameserver.gov", ["1.2.4.5"]), + ] + ( + deleted_values, + updated_values, + new_values, + oldNameservers, + ) = self.domain.getNameserverChanges(newChanges) + self.assertEqual(deleted_values, []) + self.assertEqual(updated_values, [("ns3.my-nameserver.gov", ["1.2.4.5"])]) + self.assertEqual(new_values, {}) + self.assertEqual( + oldNameservers, + {"ns3.my-nameserver.gov": ["1.2.3.4"]}, + ) def test_get_nameserver_changes_success_new_vals(self): - # Testing only creating no other changes - self.domain._cache["hosts"] = [ - {"name": "ns1.example.com", "addrs": None}, - ] - newChanges = [ - ("ns1.example.com",), - ("ns4.example.com",), - ] - ( - deleted_values, - updated_values, - new_values, - oldNameservers, - ) = self.domain.getNameserverChanges(newChanges) + with less_console_noise(): + # Testing only creating no other changes + self.domain._cache["hosts"] = [ + {"name": "ns1.example.com", "addrs": None}, + ] + newChanges = [ + ("ns1.example.com",), + ("ns4.example.com",), + ] + ( + deleted_values, + updated_values, + new_values, + oldNameservers, + ) = self.domain.getNameserverChanges(newChanges) - self.assertEqual(deleted_values, []) - self.assertEqual(updated_values, []) - self.assertEqual(new_values, {"ns4.example.com": None}) - self.assertEqual( - oldNameservers, - { - "ns1.example.com": None, - }, - ) + self.assertEqual(deleted_values, []) + self.assertEqual(updated_values, []) + self.assertEqual(new_values, {"ns4.example.com": None}) + self.assertEqual( + oldNameservers, + { + "ns1.example.com": None, + }, + ) def test_user_adds_one_nameserver(self): """ @@ -1193,32 +1156,27 @@ class TestRegistrantNameservers(MockEppLib): And `domain.is_active` returns False And domain.first_ready is null """ - - # set 1 nameserver - nameserver = "ns1.my-nameserver.com" - self.domain.nameservers = [(nameserver,)] - - # when we create a host, we should've updated at the same time - created_host = commands.CreateHost(nameserver) - update_domain_with_created = commands.UpdateDomain( - name=self.domain.name, - add=[common.HostObjSet([created_host.name])], - rem=[], - ) - - # checking if commands were sent (commands have to be sent in order) - expectedCalls = [ - call(created_host, cleaned=True), - call(update_domain_with_created, cleaned=True), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls) - - # check that status is still NOT READY - # as you have less than 2 nameservers - self.assertFalse(self.domain.is_active()) - - self.assertEqual(self.domain.first_ready, None) + with less_console_noise(): + # set 1 nameserver + nameserver = "ns1.my-nameserver.com" + self.domain.nameservers = [(nameserver,)] + # when we create a host, we should've updated at the same time + created_host = commands.CreateHost(nameserver) + update_domain_with_created = commands.UpdateDomain( + name=self.domain.name, + add=[common.HostObjSet([created_host.name])], + rem=[], + ) + # checking if commands were sent (commands have to be sent in order) + expectedCalls = [ + call(created_host, cleaned=True), + call(update_domain_with_created, cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls) + # check that status is still NOT READY + # as you have less than 2 nameservers + self.assertFalse(self.domain.is_active()) + self.assertEqual(self.domain.first_ready, None) def test_user_adds_two_nameservers(self): """ @@ -1230,36 +1188,32 @@ class TestRegistrantNameservers(MockEppLib): And `domain.is_active` returns True And domain.first_ready is not null """ - - # set 2 nameservers - self.domain.nameservers = [(self.nameserver1,), (self.nameserver2,)] - - # when you create a host, you also have to update at same time - created_host1 = commands.CreateHost(self.nameserver1) - created_host2 = commands.CreateHost(self.nameserver2) - - update_domain_with_created = commands.UpdateDomain( - name=self.domain.name, - add=[ - common.HostObjSet([created_host1.name, created_host2.name]), - ], - rem=[], - ) - - infoDomain = commands.InfoDomain(name="my-nameserver.gov", auth_info=None) - # checking if commands were sent (commands have to be sent in order) - expectedCalls = [ - call(infoDomain, cleaned=True), - call(created_host1, cleaned=True), - call(created_host2, cleaned=True), - call(update_domain_with_created, cleaned=True), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) - self.assertEqual(4, self.mockedSendFunction.call_count) - # check that status is READY - self.assertTrue(self.domain.is_active()) - self.assertNotEqual(self.domain.first_ready, None) + with less_console_noise(): + # set 2 nameservers + self.domain.nameservers = [(self.nameserver1,), (self.nameserver2,)] + # when you create a host, you also have to update at same time + created_host1 = commands.CreateHost(self.nameserver1) + created_host2 = commands.CreateHost(self.nameserver2) + update_domain_with_created = commands.UpdateDomain( + name=self.domain.name, + add=[ + common.HostObjSet([created_host1.name, created_host2.name]), + ], + rem=[], + ) + infoDomain = commands.InfoDomain(name="my-nameserver.gov", auth_info=None) + # checking if commands were sent (commands have to be sent in order) + expectedCalls = [ + call(infoDomain, cleaned=True), + call(created_host1, cleaned=True), + call(created_host2, cleaned=True), + call(update_domain_with_created, cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) + self.assertEqual(4, self.mockedSendFunction.call_count) + # check that status is READY + self.assertTrue(self.domain.is_active()) + self.assertNotEqual(self.domain.first_ready, None) def test_user_adds_too_many_nameservers(self): """ @@ -1268,43 +1222,43 @@ class TestRegistrantNameservers(MockEppLib): When `domain.nameservers` is set to an array of length 14 Then Domain raises a user-friendly error """ + with less_console_noise(): + # set 13+ nameservers + nameserver1 = "ns1.cats-are-superior1.com" + nameserver2 = "ns1.cats-are-superior2.com" + nameserver3 = "ns1.cats-are-superior3.com" + nameserver4 = "ns1.cats-are-superior4.com" + nameserver5 = "ns1.cats-are-superior5.com" + nameserver6 = "ns1.cats-are-superior6.com" + nameserver7 = "ns1.cats-are-superior7.com" + nameserver8 = "ns1.cats-are-superior8.com" + nameserver9 = "ns1.cats-are-superior9.com" + nameserver10 = "ns1.cats-are-superior10.com" + nameserver11 = "ns1.cats-are-superior11.com" + nameserver12 = "ns1.cats-are-superior12.com" + nameserver13 = "ns1.cats-are-superior13.com" + nameserver14 = "ns1.cats-are-superior14.com" - # set 13+ nameservers - nameserver1 = "ns1.cats-are-superior1.com" - nameserver2 = "ns1.cats-are-superior2.com" - nameserver3 = "ns1.cats-are-superior3.com" - nameserver4 = "ns1.cats-are-superior4.com" - nameserver5 = "ns1.cats-are-superior5.com" - nameserver6 = "ns1.cats-are-superior6.com" - nameserver7 = "ns1.cats-are-superior7.com" - nameserver8 = "ns1.cats-are-superior8.com" - nameserver9 = "ns1.cats-are-superior9.com" - nameserver10 = "ns1.cats-are-superior10.com" - nameserver11 = "ns1.cats-are-superior11.com" - nameserver12 = "ns1.cats-are-superior12.com" - nameserver13 = "ns1.cats-are-superior13.com" - nameserver14 = "ns1.cats-are-superior14.com" + def _get_14_nameservers(): + self.domain.nameservers = [ + (nameserver1,), + (nameserver2,), + (nameserver3,), + (nameserver4,), + (nameserver5,), + (nameserver6,), + (nameserver7,), + (nameserver8,), + (nameserver9), + (nameserver10,), + (nameserver11,), + (nameserver12,), + (nameserver13,), + (nameserver14,), + ] - def _get_14_nameservers(): - self.domain.nameservers = [ - (nameserver1,), - (nameserver2,), - (nameserver3,), - (nameserver4,), - (nameserver5,), - (nameserver6,), - (nameserver7,), - (nameserver8,), - (nameserver9), - (nameserver10,), - (nameserver11,), - (nameserver12,), - (nameserver13,), - (nameserver14,), - ] - - self.assertRaises(NameserverError, _get_14_nameservers) - self.assertEqual(self.mockedSendFunction.call_count, 0) + self.assertRaises(NameserverError, _get_14_nameservers) + self.assertEqual(self.mockedSendFunction.call_count, 0) def test_user_removes_some_nameservers(self): """ @@ -1315,37 +1269,36 @@ class TestRegistrantNameservers(MockEppLib): to the registry And `domain.is_active` returns True """ - - # Mock is set to return 3 nameservers on infodomain - self.domainWithThreeNS.nameservers = [(self.nameserver1,), (self.nameserver2,)] - expectedCalls = [ - # calls info domain, and info on all hosts - # to get past values - # then removes the single host and updates domain - call( - commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), - call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), - call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), - call( - commands.UpdateDomain( - name=self.domainWithThreeNS.name, - add=[], - rem=[common.HostObjSet(hosts=["ns1.cats-are-superior3.com"])], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + with less_console_noise(): + # Mock is set to return 3 nameservers on infodomain + self.domainWithThreeNS.nameservers = [(self.nameserver1,), (self.nameserver2,)] + expectedCalls = [ + # calls info domain, and info on all hosts + # to get past values + # then removes the single host and updates domain + call( + commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), + cleaned=True, ), - cleaned=True, - ), - call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) - self.assertTrue(self.domainWithThreeNS.is_active()) + call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), + call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), + call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), + call( + commands.UpdateDomain( + name=self.domainWithThreeNS.name, + add=[], + rem=[common.HostObjSet(hosts=["ns1.cats-are-superior3.com"])], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ), + call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) + self.assertTrue(self.domainWithThreeNS.is_active()) def test_user_removes_too_many_nameservers(self): """ @@ -1357,41 +1310,40 @@ class TestRegistrantNameservers(MockEppLib): And `domain.is_active` returns False """ - - self.domainWithThreeNS.nameservers = [(self.nameserver1,)] - expectedCalls = [ - call( - commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), - call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), - call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), - call(commands.DeleteHost(name="ns1.my-nameserver-2.com"), cleaned=True), - call( - commands.UpdateDomain( - name=self.domainWithThreeNS.name, - add=[], - rem=[ - common.HostObjSet( - hosts=[ - "ns1.my-nameserver-2.com", - "ns1.cats-are-superior3.com", - ] - ), - ], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + with less_console_noise(): + self.domainWithThreeNS.nameservers = [(self.nameserver1,)] + expectedCalls = [ + call( + commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), + cleaned=True, ), - cleaned=True, - ), - call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) - self.assertFalse(self.domainWithThreeNS.is_active()) + call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), + call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), + call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), + call(commands.DeleteHost(name="ns1.my-nameserver-2.com"), cleaned=True), + call( + commands.UpdateDomain( + name=self.domainWithThreeNS.name, + add=[], + rem=[ + common.HostObjSet( + hosts=[ + "ns1.my-nameserver-2.com", + "ns1.cats-are-superior3.com", + ] + ), + ], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ), + call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) + self.assertFalse(self.domainWithThreeNS.is_active()) def test_user_replaces_nameservers(self): """ @@ -1403,59 +1355,58 @@ class TestRegistrantNameservers(MockEppLib): And `commands.UpdateDomain` is sent to add #4 and #5 plus remove #2 and #3 And `commands.DeleteHost` is sent to delete #2 and #3 """ - self.domainWithThreeNS.nameservers = [ - (self.nameserver1,), - ("ns1.cats-are-superior1.com",), - ("ns1.cats-are-superior2.com",), - ] - - expectedCalls = [ - call( - commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), - call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), - call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), - call(commands.DeleteHost(name="ns1.my-nameserver-2.com"), cleaned=True), - call( - commands.CreateHost(name="ns1.cats-are-superior1.com", addrs=[]), - cleaned=True, - ), - call( - commands.CreateHost(name="ns1.cats-are-superior2.com", addrs=[]), - cleaned=True, - ), - call( - commands.UpdateDomain( - name=self.domainWithThreeNS.name, - add=[ - common.HostObjSet( - hosts=[ - "ns1.cats-are-superior1.com", - "ns1.cats-are-superior2.com", - ] - ), - ], - rem=[ - common.HostObjSet( - hosts=[ - "ns1.my-nameserver-2.com", - "ns1.cats-are-superior3.com", - ] - ), - ], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + with less_console_noise(): + self.domainWithThreeNS.nameservers = [ + (self.nameserver1,), + ("ns1.cats-are-superior1.com",), + ("ns1.cats-are-superior2.com",), + ] + expectedCalls = [ + call( + commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), + cleaned=True, ), - cleaned=True, - ), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) - self.assertTrue(self.domainWithThreeNS.is_active()) + call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), + call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), + call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), + call(commands.DeleteHost(name="ns1.my-nameserver-2.com"), cleaned=True), + call( + commands.CreateHost(name="ns1.cats-are-superior1.com", addrs=[]), + cleaned=True, + ), + call( + commands.CreateHost(name="ns1.cats-are-superior2.com", addrs=[]), + cleaned=True, + ), + call( + commands.UpdateDomain( + name=self.domainWithThreeNS.name, + add=[ + common.HostObjSet( + hosts=[ + "ns1.cats-are-superior1.com", + "ns1.cats-are-superior2.com", + ] + ), + ], + rem=[ + common.HostObjSet( + hosts=[ + "ns1.my-nameserver-2.com", + "ns1.cats-are-superior3.com", + ] + ), + ], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) + self.assertTrue(self.domainWithThreeNS.is_active()) def test_user_cannot_add_subordinate_without_ip(self): """ @@ -1465,11 +1416,10 @@ class TestRegistrantNameservers(MockEppLib): with a subdomain of the domain and no IP addresses Then Domain raises a user-friendly error """ - - dotgovnameserver = "my-nameserver.gov" - - with self.assertRaises(NameserverError): - self.domain.nameservers = [(dotgovnameserver,)] + with less_console_noise(): + dotgovnameserver = "my-nameserver.gov" + with self.assertRaises(NameserverError): + self.domain.nameservers = [(dotgovnameserver,)] def test_user_updates_ips(self): """ @@ -1480,46 +1430,45 @@ class TestRegistrantNameservers(MockEppLib): with a different IP address(es) Then `commands.UpdateHost` is sent to the registry """ - domain, _ = Domain.objects.get_or_create(name="nameserverwithip.gov", state=Domain.State.READY) - domain.nameservers = [ - ("ns1.nameserverwithip.gov", ["2.3.4.5", "1.2.3.4"]), - ( - "ns2.nameserverwithip.gov", - ["1.2.3.4", "2.3.4.5", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"], - ), - ("ns3.nameserverwithip.gov", ["2.3.4.5"]), - ] - - expectedCalls = [ - call( - commands.InfoDomain(name="nameserverwithip.gov", auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="ns1.nameserverwithip.gov"), cleaned=True), - call(commands.InfoHost(name="ns2.nameserverwithip.gov"), cleaned=True), - call(commands.InfoHost(name="ns3.nameserverwithip.gov"), cleaned=True), - call( - commands.UpdateHost( - name="ns2.nameserverwithip.gov", - add=[common.Ip(addr="2001:0db8:85a3:0000:0000:8a2e:0370:7334", ip="v6")], - rem=[], - chg=None, + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="nameserverwithip.gov", state=Domain.State.READY) + domain.nameservers = [ + ("ns1.nameserverwithip.gov", ["2.3.4.5", "1.2.3.4"]), + ( + "ns2.nameserverwithip.gov", + ["1.2.3.4", "2.3.4.5", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"], ), - cleaned=True, - ), - call( - commands.UpdateHost( - name="ns3.nameserverwithip.gov", - add=[], - rem=[common.Ip(addr="1.2.3.4", ip=None)], - chg=None, + ("ns3.nameserverwithip.gov", ["2.3.4.5"]), + ] + expectedCalls = [ + call( + commands.InfoDomain(name="nameserverwithip.gov", auth_info=None), + cleaned=True, ), - cleaned=True, - ), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) - self.assertTrue(domain.is_active()) + call(commands.InfoHost(name="ns1.nameserverwithip.gov"), cleaned=True), + call(commands.InfoHost(name="ns2.nameserverwithip.gov"), cleaned=True), + call(commands.InfoHost(name="ns3.nameserverwithip.gov"), cleaned=True), + call( + commands.UpdateHost( + name="ns2.nameserverwithip.gov", + add=[common.Ip(addr="2001:0db8:85a3:0000:0000:8a2e:0370:7334", ip="v6")], + rem=[], + chg=None, + ), + cleaned=True, + ), + call( + commands.UpdateHost( + name="ns3.nameserverwithip.gov", + add=[], + rem=[common.Ip(addr="1.2.3.4", ip=None)], + chg=None, + ), + cleaned=True, + ), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) + self.assertTrue(domain.is_active()) def test_user_cannot_add_non_subordinate_with_ip(self): """ @@ -1529,10 +1478,10 @@ class TestRegistrantNameservers(MockEppLib): which is not a subdomain of the domain and has IP addresses Then Domain raises a user-friendly error """ - dotgovnameserver = "mynameserverdotgov.gov" - - with self.assertRaises(NameserverError): - self.domain.nameservers = [(dotgovnameserver, ["1.2.3"])] + with less_console_noise(): + dotgovnameserver = "mynameserverdotgov.gov" + with self.assertRaises(NameserverError): + self.domain.nameservers = [(dotgovnameserver, ["1.2.3"])] def test_nameservers_are_idempotent(self): """ @@ -1541,60 +1490,60 @@ class TestRegistrantNameservers(MockEppLib): to the registry twice with identical data Then no errors are raised in Domain """ - - # Checking that it doesn't create or update even if out of order - self.domainWithThreeNS.nameservers = [ - (self.nameserver3,), - (self.nameserver1,), - (self.nameserver2,), - ] - - expectedCalls = [ - call( - commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), - cleaned=True, - ), - call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), - call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), - call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), - ] - - self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) - self.assertEqual(self.mockedSendFunction.call_count, 4) + with less_console_noise(): + # Checking that it doesn't create or update even if out of order + self.domainWithThreeNS.nameservers = [ + (self.nameserver3,), + (self.nameserver1,), + (self.nameserver2,), + ] + expectedCalls = [ + call( + commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None), + cleaned=True, + ), + call(commands.InfoHost(name="ns1.my-nameserver-1.com"), cleaned=True), + call(commands.InfoHost(name="ns1.my-nameserver-2.com"), cleaned=True), + call(commands.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True), + ] + self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True) + self.assertEqual(self.mockedSendFunction.call_count, 4) def test_is_subdomain_with_no_ip(self): - domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY) - - with self.assertRaises(NameserverError): - domain.nameservers = [ - ("ns1.nameserversubdomain.gov",), - ("ns2.nameserversubdomain.gov",), - ] + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY) + with self.assertRaises(NameserverError): + domain.nameservers = [ + ("ns1.nameserversubdomain.gov",), + ("ns2.nameserversubdomain.gov",), + ] def test_not_subdomain_but_has_ip(self): - domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY) - - with self.assertRaises(NameserverError): - domain.nameservers = [ - ("ns1.cats-da-best.gov", ["1.2.3.4"]), - ("ns2.cats-da-best.gov", ["2.3.4.5"]), - ] + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY) + with self.assertRaises(NameserverError): + domain.nameservers = [ + ("ns1.cats-da-best.gov", ["1.2.3.4"]), + ("ns2.cats-da-best.gov", ["2.3.4.5"]), + ] def test_is_subdomain_but_ip_addr_not_valid(self): - domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY) - with self.assertRaises(NameserverError): - domain.nameservers = [ - ("ns1.nameserversubdomain.gov", ["1.2.3"]), - ("ns2.nameserversubdomain.gov", ["2.3.4"]), - ] + with self.assertRaises(NameserverError): + domain.nameservers = [ + ("ns1.nameserversubdomain.gov", ["1.2.3"]), + ("ns2.nameserversubdomain.gov", ["2.3.4"]), + ] def test_setting_not_allowed(self): """Scenario: A domain state is not Ready or DNS needed then setting nameservers is not allowed""" - domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD) - with self.assertRaises(ActionNotAllowed): - domain.nameservers = [self.nameserver1, self.nameserver2] + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD) + with self.assertRaises(ActionNotAllowed): + domain.nameservers = [self.nameserver1, self.nameserver2] def test_nameserver_returns_on_registry_error(self): """ @@ -1602,28 +1551,25 @@ class TestRegistrantNameservers(MockEppLib): Registry is unavailable and throws exception when attempting to build cache from registry. Nameservers retrieved from database. """ - domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) - # set the host and host_ips directly in the database; this is normally handled through - # fetch_cache - host, _ = Host.objects.get_or_create(domain=domain, name="ns1.fake.gov") - host_ip, _ = HostIP.objects.get_or_create(host=host, address="1.1.1.1") + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) + # set the host and host_ips directly in the database; this is normally handled through + # fetch_cache + host, _ = Host.objects.get_or_create(domain=domain, name="ns1.fake.gov") + host_ip, _ = HostIP.objects.get_or_create(host=host, address="1.1.1.1") - # mock that registry throws an error on the InfoHost send + # mock that registry throws an error on the InfoHost send + def side_effect(_request, cleaned): + raise RegistryError(code=ErrorCode.COMMAND_FAILED) - def side_effect(_request, cleaned): - raise RegistryError(code=ErrorCode.COMMAND_FAILED) - - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - nameservers = domain.nameservers - - self.assertEqual(len(nameservers), 1) - self.assertEqual(nameservers[0][0], "ns1.fake.gov") - self.assertEqual(nameservers[0][1], ["1.1.1.1"]) - - patcher.stop() + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + nameservers = domain.nameservers + self.assertEqual(len(nameservers), 1) + self.assertEqual(nameservers[0][0], "ns1.fake.gov") + self.assertEqual(nameservers[0][1], ["1.1.1.1"]) + patcher.stop() def test_nameservers_stored_on_fetch_cache(self): """ @@ -1633,24 +1579,23 @@ class TestRegistrantNameservers(MockEppLib): of 'fake.host.com' from InfoDomain and an array of 2 IPs: 1.2.3.4 and 2.3.4.5 from InfoHost """ - domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) - - # mock the get_or_create methods for Host and HostIP - with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object( - HostIP.objects, "get_or_create" - ) as mock_host_ip_get_or_create: - # Set the return value for the mocks - mock_host_get_or_create.return_value = (Host(), True) - mock_host_ip_get_or_create.return_value = (HostIP(), True) - - # force fetch_cache to be called, which will return above documented mocked hosts - domain.nameservers - # assert that the mocks are called - mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.host.com") - # Retrieve the mocked_host from the return value of the mock - actual_mocked_host, _ = mock_host_get_or_create.return_value - mock_host_ip_get_or_create.assert_called_with(address="2.3.4.5", host=actual_mocked_host) - self.assertEqual(mock_host_ip_get_or_create.call_count, 2) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) + # mock the get_or_create methods for Host and HostIP + with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object( + HostIP.objects, "get_or_create" + ) as mock_host_ip_get_or_create: + # Set the return value for the mocks + mock_host_get_or_create.return_value = (Host(), True) + mock_host_ip_get_or_create.return_value = (HostIP(), True) + # force fetch_cache to be called, which will return above documented mocked hosts + domain.nameservers + # assert that the mocks are called + mock_host_get_or_create.assert_called_once_with(domain=domain, name="fake.host.com") + # Retrieve the mocked_host from the return value of the mock + actual_mocked_host, _ = mock_host_get_or_create.return_value + mock_host_ip_get_or_create.assert_called_with(address="2.3.4.5", host=actual_mocked_host) + self.assertEqual(mock_host_ip_get_or_create.call_count, 2) @skip("not implemented yet") def test_update_is_unsuccessful(self): @@ -1791,54 +1736,51 @@ class TestRegistrantDNSSEC(MockEppLib): else: return MagicMock(res_data=[self.mockDataInfoHosts]) - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") - domain.dnssecdata = self.dnssecExtensionWithDsData - - # get the DNS SEC extension added to the UpdateDomain command and - # verify that it is properly sent - # args[0] is the _request sent to registry - args, _ = mocked_send.call_args - # assert that the extension on the update matches - self.assertEquals( - args[0].extensions[0], - self.createUpdateExtension(self.dnssecExtensionWithDsData), - ) - # test that the dnssecdata getter is functioning properly - dnssecdata_get = domain.dnssecdata - mocked_send.assert_has_calls( - [ - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", + with less_console_noise(): + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") + domain.dnssecdata = self.dnssecExtensionWithDsData + # get the DNS SEC extension added to the UpdateDomain command and + # verify that it is properly sent + # args[0] is the _request sent to registry + args, _ = mocked_send.call_args + # assert that the extension on the update matches + self.assertEquals( + args[0].extensions[0], + self.createUpdateExtension(self.dnssecExtensionWithDsData), + ) + # test that the dnssecdata getter is functioning properly + dnssecdata_get = domain.dnssecdata + mocked_send.assert_has_calls( + [ + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.UpdateDomain( - name="dnssec-dsdata.gov", - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + call( + commands.UpdateDomain( + name="dnssec-dsdata.gov", + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - ] - ) - - self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData) - - patcher.stop() + ] + ) + self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData) + patcher.stop() def test_dnssec_is_idempotent(self): """ @@ -1872,54 +1814,51 @@ class TestRegistrantDNSSEC(MockEppLib): else: return MagicMock(res_data=[self.mockDataInfoHosts]) - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") - - # set the dnssecdata once - domain.dnssecdata = self.dnssecExtensionWithDsData - # set the dnssecdata again - domain.dnssecdata = self.dnssecExtensionWithDsData - # test that the dnssecdata getter is functioning properly - dnssecdata_get = domain.dnssecdata - mocked_send.assert_has_calls( - [ - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", + with less_console_noise(): + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") + # set the dnssecdata once + domain.dnssecdata = self.dnssecExtensionWithDsData + # set the dnssecdata again + domain.dnssecdata = self.dnssecExtensionWithDsData + # test that the dnssecdata getter is functioning properly + dnssecdata_get = domain.dnssecdata + mocked_send.assert_has_calls( + [ + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.UpdateDomain( - name="dnssec-dsdata.gov", - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + call( + commands.UpdateDomain( + name="dnssec-dsdata.gov", + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - ] - ) - - self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData) - - patcher.stop() + ] + ) + self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData) + patcher.stop() def test_user_adds_dnssec_data_multiple_dsdata(self): """ @@ -1949,48 +1888,45 @@ class TestRegistrantDNSSEC(MockEppLib): else: return MagicMock(res_data=[self.mockDataInfoHosts]) - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov") - - domain.dnssecdata = self.dnssecExtensionWithMultDsData - # get the DNS SEC extension added to the UpdateDomain command - # and verify that it is properly sent - # args[0] is the _request sent to registry - args, _ = mocked_send.call_args - # assert that the extension matches - self.assertEquals( - args[0].extensions[0], - self.createUpdateExtension(self.dnssecExtensionWithMultDsData), - ) - # test that the dnssecdata getter is functioning properly - dnssecdata_get = domain.dnssecdata - mocked_send.assert_has_calls( - [ - call( - commands.UpdateDomain( - name="dnssec-multdsdata.gov", - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + with less_console_noise(): + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov") + domain.dnssecdata = self.dnssecExtensionWithMultDsData + # get the DNS SEC extension added to the UpdateDomain command + # and verify that it is properly sent + # args[0] is the _request sent to registry + args, _ = mocked_send.call_args + # assert that the extension matches + self.assertEquals( + args[0].extensions[0], + self.createUpdateExtension(self.dnssecExtensionWithMultDsData), + ) + # test that the dnssecdata getter is functioning properly + dnssecdata_get = domain.dnssecdata + mocked_send.assert_has_calls( + [ + call( + commands.UpdateDomain( + name="dnssec-multdsdata.gov", + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.InfoDomain( - name="dnssec-multdsdata.gov", + call( + commands.InfoDomain( + name="dnssec-multdsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - ] - ) - - self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData) - - patcher.stop() + ] + ) + self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData) + patcher.stop() def test_user_removes_dnssec_data(self): """ @@ -2021,65 +1957,64 @@ class TestRegistrantDNSSEC(MockEppLib): else: return MagicMock(res_data=[self.mockDataInfoHosts]) - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") - # dnssecdata_get_initial = domain.dnssecdata # call to force initial mock - # domain._invalidate_cache() - domain.dnssecdata = self.dnssecExtensionWithDsData - domain.dnssecdata = self.dnssecExtensionRemovingDsData - # get the DNS SEC extension added to the UpdateDomain command and - # verify that it is properly sent - # args[0] is the _request sent to registry - args, _ = mocked_send.call_args - # assert that the extension on the update matches - self.assertEquals( - args[0].extensions[0], - self.createUpdateExtension( - self.dnssecExtensionWithDsData, - remove=True, - ), - ) - mocked_send.assert_has_calls( - [ - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", - ), - cleaned=True, + with less_console_noise(): + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") + # dnssecdata_get_initial = domain.dnssecdata # call to force initial mock + # domain._invalidate_cache() + domain.dnssecdata = self.dnssecExtensionWithDsData + domain.dnssecdata = self.dnssecExtensionRemovingDsData + # get the DNS SEC extension added to the UpdateDomain command and + # verify that it is properly sent + # args[0] is the _request sent to registry + args, _ = mocked_send.call_args + # assert that the extension on the update matches + self.assertEquals( + args[0].extensions[0], + self.createUpdateExtension( + self.dnssecExtensionWithDsData, + remove=True, ), - call( - commands.UpdateDomain( - name="dnssec-dsdata.gov", - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + ) + mocked_send.assert_has_calls( + [ + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.InfoDomain( - name="dnssec-dsdata.gov", + call( + commands.UpdateDomain( + name="dnssec-dsdata.gov", + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, ), - cleaned=True, - ), - call( - commands.UpdateDomain( - name="dnssec-dsdata.gov", - nsset=None, - keyset=None, - registrant=None, - auth_info=None, + call( + commands.InfoDomain( + name="dnssec-dsdata.gov", + ), + cleaned=True, ), - cleaned=True, - ), - ] - ) - - patcher.stop() + call( + commands.UpdateDomain( + name="dnssec-dsdata.gov", + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ), + ] + ) + patcher.stop() def test_update_is_unsuccessful(self): """ @@ -2087,12 +2022,11 @@ class TestRegistrantDNSSEC(MockEppLib): When an error is returned from epplibwrapper Then a user-friendly error message is returned for displaying on the web """ - - domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov") - - with self.assertRaises(RegistryError) as err: - domain.dnssecdata = self.dnssecExtensionWithDsData - self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error()) + with less_console_noise(): + domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov") + with self.assertRaises(RegistryError) as err: + domain.dnssecdata = self.dnssecExtensionWithDsData + self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error()) class TestExpirationDate(MockEppLib): @@ -2117,44 +2051,49 @@ class TestExpirationDate(MockEppLib): def test_expiration_date_setter_not_implemented(self): """assert that the setter for expiration date is not implemented and will raise error""" - with self.assertRaises(NotImplementedError): - self.domain.registry_expiration_date = datetime.date.today() + with less_console_noise(): + with self.assertRaises(NotImplementedError): + self.domain.registry_expiration_date = datetime.date.today() def test_renew_domain(self): """assert that the renew_domain sets new expiration date in cache and saves to registrar""" - self.domain.renew_domain() - test_date = datetime.date(2023, 5, 25) - self.assertEquals(self.domain._cache["ex_date"], test_date) - self.assertEquals(self.domain.expiration_date, test_date) + with less_console_noise(): + self.domain.renew_domain() + test_date = datetime.date(2023, 5, 25) + self.assertEquals(self.domain._cache["ex_date"], test_date) + self.assertEquals(self.domain.expiration_date, test_date) def test_renew_domain_error(self): """assert that the renew_domain raises an exception when registry raises error""" - with self.assertRaises(RegistryError): - self.domain_w_error.renew_domain() + with less_console_noise(): + with self.assertRaises(RegistryError): + self.domain_w_error.renew_domain() def test_is_expired(self): """assert that is_expired returns true for expiration_date in past""" - # force fetch_cache to be called - self.domain.statuses - self.assertTrue(self.domain.is_expired) + with less_console_noise(): + # force fetch_cache to be called + self.domain.statuses + self.assertTrue(self.domain.is_expired) def test_is_not_expired(self): """assert that is_expired returns false for expiration in future""" - # to do this, need to mock value returned from timezone.now - # set now to 2023-01-01 - mocked_datetime = datetime.datetime(2023, 1, 1, 12, 0, 0) - # force fetch_cache which sets the expiration date to 2023-05-25 - self.domain.statuses - - with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime): - self.assertFalse(self.domain.is_expired()) + with less_console_noise(): + # to do this, need to mock value returned from timezone.now + # set now to 2023-01-01 + mocked_datetime = datetime.datetime(2023, 1, 1, 12, 0, 0) + # force fetch_cache which sets the expiration date to 2023-05-25 + self.domain.statuses + with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime): + self.assertFalse(self.domain.is_expired()) def test_expiration_date_updated_on_info_domain_call(self): """assert that expiration date in db is updated on info domain call""" - # force fetch_cache to be called - self.domain.statuses - test_date = datetime.date(2023, 5, 25) - self.assertEquals(self.domain.expiration_date, test_date) + with less_console_noise(): + # force fetch_cache to be called + self.domain.statuses + test_date = datetime.date(2023, 5, 25) + self.assertEquals(self.domain.expiration_date, test_date) class TestCreationDate(MockEppLib): @@ -2169,7 +2108,7 @@ class TestCreationDate(MockEppLib): self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) # creation_date returned from mockDataInfoDomain with creation date: # cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35) - self.creation_date = datetime.datetime(2023, 5, 25, 19, 45, 35) + self.creation_date = make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)) def tearDown(self): Domain.objects.all().delete() @@ -2212,29 +2151,30 @@ class TestAnalystClientHold(MockEppLib): When `domain.place_client_hold()` is called Then `CLIENT_HOLD` is added to the domain's statuses """ - self.domain.place_client_hold() - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.UpdateDomain( - name="fake.gov", - add=[ - common.Status( - state=Domain.Status.CLIENT_HOLD, - description="", - lang="en", - ) - ], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, - ), - cleaned=True, - ) - ] - ) - self.assertEquals(self.domain.state, Domain.State.ON_HOLD) + with less_console_noise(): + self.domain.place_client_hold() + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateDomain( + name="fake.gov", + add=[ + common.Status( + state=Domain.Status.CLIENT_HOLD, + description="", + lang="en", + ) + ], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ) + ] + ) + self.assertEquals(self.domain.state, Domain.State.ON_HOLD) def test_analyst_places_client_hold_idempotent(self): """ @@ -2243,29 +2183,30 @@ class TestAnalystClientHold(MockEppLib): When `domain.place_client_hold()` is called Then Domain returns normally (without error) """ - self.domain_on_hold.place_client_hold() - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.UpdateDomain( - name="fake-on-hold.gov", - add=[ - common.Status( - state=Domain.Status.CLIENT_HOLD, - description="", - lang="en", - ) - ], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, - ), - cleaned=True, - ) - ] - ) - self.assertEquals(self.domain_on_hold.state, Domain.State.ON_HOLD) + with less_console_noise(): + self.domain_on_hold.place_client_hold() + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateDomain( + name="fake-on-hold.gov", + add=[ + common.Status( + state=Domain.Status.CLIENT_HOLD, + description="", + lang="en", + ) + ], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ) + ] + ) + self.assertEquals(self.domain_on_hold.state, Domain.State.ON_HOLD) def test_analyst_removes_client_hold(self): """ @@ -2274,29 +2215,30 @@ class TestAnalystClientHold(MockEppLib): When `domain.remove_client_hold()` is called Then `CLIENT_HOLD` is no longer in the domain's statuses """ - self.domain_on_hold.revert_client_hold() - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.UpdateDomain( - name="fake-on-hold.gov", - rem=[ - common.Status( - state=Domain.Status.CLIENT_HOLD, - description="", - lang="en", - ) - ], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, - ), - cleaned=True, - ) - ] - ) - self.assertEquals(self.domain_on_hold.state, Domain.State.READY) + with less_console_noise(): + self.domain_on_hold.revert_client_hold() + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateDomain( + name="fake-on-hold.gov", + rem=[ + common.Status( + state=Domain.Status.CLIENT_HOLD, + description="", + lang="en", + ) + ], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ) + ] + ) + self.assertEquals(self.domain_on_hold.state, Domain.State.READY) def test_analyst_removes_client_hold_idempotent(self): """ @@ -2305,29 +2247,30 @@ class TestAnalystClientHold(MockEppLib): When `domain.remove_client_hold()` is called Then Domain returns normally (without error) """ - self.domain.revert_client_hold() - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.UpdateDomain( - name="fake.gov", - rem=[ - common.Status( - state=Domain.Status.CLIENT_HOLD, - description="", - lang="en", - ) - ], - nsset=None, - keyset=None, - registrant=None, - auth_info=None, - ), - cleaned=True, - ) - ] - ) - self.assertEquals(self.domain.state, Domain.State.READY) + with less_console_noise(): + self.domain.revert_client_hold() + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateDomain( + name="fake.gov", + rem=[ + common.Status( + state=Domain.Status.CLIENT_HOLD, + description="", + lang="en", + ) + ], + nsset=None, + keyset=None, + registrant=None, + auth_info=None, + ), + cleaned=True, + ) + ] + ) + self.assertEquals(self.domain.state, Domain.State.READY) def test_update_is_unsuccessful(self): """ @@ -2339,18 +2282,17 @@ class TestAnalystClientHold(MockEppLib): def side_effect(_request, cleaned): raise RegistryError(code=ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION) - patcher = patch("registrar.models.domain.registry.send") - mocked_send = patcher.start() - mocked_send.side_effect = side_effect - - # if RegistryError is raised, admin formats user-friendly - # error message if error is_client_error, is_session_error, or - # is_server_error; so test for those conditions - with self.assertRaises(RegistryError) as err: - self.domain.place_client_hold() - self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error()) - - patcher.stop() + with less_console_noise(): + patcher = patch("registrar.models.domain.registry.send") + mocked_send = patcher.start() + mocked_send.side_effect = side_effect + # if RegistryError is raised, admin formats user-friendly + # error message if error is_client_error, is_session_error, or + # is_server_error; so test for those conditions + with self.assertRaises(RegistryError) as err: + self.domain.place_client_hold() + self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error()) + patcher.stop() class TestAnalystLock(TestCase): @@ -2443,31 +2385,28 @@ class TestAnalystDelete(MockEppLib): The deleted date is set. """ - # Put the domain in client hold - self.domain.place_client_hold() - # Delete it... - self.domain.deletedInEpp() - self.domain.save() - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.DeleteDomain(name="fake.gov"), - cleaned=True, - ) - ] - ) - - # Domain itself should not be deleted - self.assertNotEqual(self.domain, None) - - # Domain should have the right state - self.assertEqual(self.domain.state, Domain.State.DELETED) - - # Domain should have a deleted - self.assertNotEqual(self.domain.deleted, None) - - # Cache should be invalidated - self.assertEqual(self.domain._cache, {}) + with less_console_noise(): + # Put the domain in client hold + self.domain.place_client_hold() + # Delete it... + self.domain.deletedInEpp() + self.domain.save() + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.DeleteDomain(name="fake.gov"), + cleaned=True, + ) + ] + ) + # Domain itself should not be deleted + self.assertNotEqual(self.domain, None) + # Domain should have the right state + self.assertEqual(self.domain.state, Domain.State.DELETED) + # Domain should have a deleted + self.assertNotEqual(self.domain.deleted, None) + # Cache should be invalidated + self.assertEqual(self.domain._cache, {}) def test_deletion_is_unsuccessful(self): """ @@ -2476,29 +2415,28 @@ class TestAnalystDelete(MockEppLib): Then a client error is returned of code 2305 And `state` is not set to `DELETED` """ - # Desired domain - domain, _ = Domain.objects.get_or_create(name="failDelete.gov", state=Domain.State.ON_HOLD) - # Put the domain in client hold - domain.place_client_hold() - - # Delete it - with self.assertRaises(RegistryError) as err: - domain.deletedInEpp() - domain.save() - self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION) - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.DeleteDomain(name="failDelete.gov"), - cleaned=True, - ) - ] - ) - - # Domain itself should not be deleted - self.assertNotEqual(domain, None) - # State should not have changed - self.assertEqual(domain.state, Domain.State.ON_HOLD) + with less_console_noise(): + # Desired domain + domain, _ = Domain.objects.get_or_create(name="failDelete.gov", state=Domain.State.ON_HOLD) + # Put the domain in client hold + domain.place_client_hold() + # Delete it + with self.assertRaises(RegistryError) as err: + domain.deletedInEpp() + domain.save() + self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION) + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.DeleteDomain(name="failDelete.gov"), + cleaned=True, + ) + ] + ) + # Domain itself should not be deleted + self.assertNotEqual(domain, None) + # State should not have changed + self.assertEqual(domain.state, Domain.State.ON_HOLD) def test_deletion_ready_fsm_failure(self): """ @@ -2511,15 +2449,15 @@ class TestAnalystDelete(MockEppLib): The deleted date is still null. """ - self.assertEqual(self.domain.state, Domain.State.READY) - with self.assertRaises(TransitionNotAllowed) as err: - self.domain.deletedInEpp() - self.domain.save() - self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION) - # Domain should not be deleted - self.assertNotEqual(self.domain, None) - # Domain should have the right state - self.assertEqual(self.domain.state, Domain.State.READY) - - # deleted should be null - self.assertEqual(self.domain.deleted, None) + with less_console_noise(): + self.assertEqual(self.domain.state, Domain.State.READY) + with self.assertRaises(TransitionNotAllowed) as err: + self.domain.deletedInEpp() + self.domain.save() + self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION) + # Domain should not be deleted + self.assertNotEqual(self.domain, None) + # Domain should have the right state + self.assertEqual(self.domain.state, Domain.State.READY) + # deleted should be null + self.assertEqual(self.domain.deleted, None) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index a85fb5849..630904218 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -23,6 +23,7 @@ import boto3_mocking from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore from datetime import date, datetime, timedelta from django.utils import timezone +from .common import less_console_noise class CsvReportsTest(TestCase): @@ -80,41 +81,43 @@ class CsvReportsTest(TestCase): @boto3_mocking.patching def test_generate_federal_report(self): """Ensures that we correctly generate current-federal.csv""" - mock_client = MagicMock() - fake_open = mock_open() - expected_file_content = [ - call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"), - call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), - call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), - ] - # We don't actually want to write anything for a test case, - # we just want to verify what is being written. - with boto3_mocking.clients.handler_for("s3", mock_client): - with patch("builtins.open", fake_open): - call_command("generate_current_federal_report", checkpath=False) - content = fake_open() + with less_console_noise(): + mock_client = MagicMock() + fake_open = mock_open() + expected_file_content = [ + call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"), + call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), + call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), + ] + # We don't actually want to write anything for a test case, + # we just want to verify what is being written. + with boto3_mocking.clients.handler_for("s3", mock_client): + with patch("builtins.open", fake_open): + call_command("generate_current_federal_report", checkpath=False) + content = fake_open() - content.write.assert_has_calls(expected_file_content) + content.write.assert_has_calls(expected_file_content) @boto3_mocking.patching def test_generate_full_report(self): """Ensures that we correctly generate current-full.csv""" - mock_client = MagicMock() - fake_open = mock_open() - expected_file_content = [ - call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"), - call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), - call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), - call("adomain2.gov,Interstate,,,,, \r\n"), - ] - # We don't actually want to write anything for a test case, - # we just want to verify what is being written. - with boto3_mocking.clients.handler_for("s3", mock_client): - with patch("builtins.open", fake_open): - call_command("generate_current_full_report", checkpath=False) - content = fake_open() + with less_console_noise(): + mock_client = MagicMock() + fake_open = mock_open() + expected_file_content = [ + call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"), + call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), + call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), + call("adomain2.gov,Interstate,,,,, \r\n"), + ] + # We don't actually want to write anything for a test case, + # we just want to verify what is being written. + with boto3_mocking.clients.handler_for("s3", mock_client): + with patch("builtins.open", fake_open): + call_command("generate_current_full_report", checkpath=False) + content = fake_open() - content.write.assert_has_calls(expected_file_content) + content.write.assert_has_calls(expected_file_content) @boto3_mocking.patching def test_not_found_full_report(self): @@ -123,19 +126,20 @@ class CsvReportsTest(TestCase): def side_effect(Bucket, Key): raise ClientError({"Error": {"Code": "NoSuchKey", "Message": "No such key"}}, "get_object") - mock_client = MagicMock() - mock_client.get_object.side_effect = side_effect + with less_console_noise(): + mock_client = MagicMock() + mock_client.get_object.side_effect = side_effect - response = None - with boto3_mocking.clients.handler_for("s3", mock_client): - with patch("boto3.client", return_value=mock_client): - with self.assertRaises(S3ClientError) as context: - response = self.client.get("/api/v1/get-report/current-full") - # Check that the response has status code 500 - self.assertEqual(response.status_code, 500) + response = None + with boto3_mocking.clients.handler_for("s3", mock_client): + with patch("boto3.client", return_value=mock_client): + with self.assertRaises(S3ClientError) as context: + response = self.client.get("/api/v1/get-report/current-full") + # Check that the response has status code 500 + self.assertEqual(response.status_code, 500) - # Check that we get the right error back from the page - self.assertEqual(context.exception.code, S3ClientErrorCodes.FILE_NOT_FOUND_ERROR) + # Check that we get the right error back from the page + self.assertEqual(context.exception.code, S3ClientErrorCodes.FILE_NOT_FOUND_ERROR) @boto3_mocking.patching def test_not_found_federal_report(self): @@ -144,83 +148,86 @@ class CsvReportsTest(TestCase): def side_effect(Bucket, Key): raise ClientError({"Error": {"Code": "NoSuchKey", "Message": "No such key"}}, "get_object") - mock_client = MagicMock() - mock_client.get_object.side_effect = side_effect + with less_console_noise(): + mock_client = MagicMock() + mock_client.get_object.side_effect = side_effect - with boto3_mocking.clients.handler_for("s3", mock_client): - with patch("boto3.client", return_value=mock_client): - with self.assertRaises(S3ClientError) as context: - response = self.client.get("/api/v1/get-report/current-federal") - # Check that the response has status code 500 - self.assertEqual(response.status_code, 500) + with boto3_mocking.clients.handler_for("s3", mock_client): + with patch("boto3.client", return_value=mock_client): + with self.assertRaises(S3ClientError) as context: + response = self.client.get("/api/v1/get-report/current-federal") + # Check that the response has status code 500 + self.assertEqual(response.status_code, 500) - # Check that we get the right error back from the page - self.assertEqual(context.exception.code, S3ClientErrorCodes.FILE_NOT_FOUND_ERROR) + # Check that we get the right error back from the page + self.assertEqual(context.exception.code, S3ClientErrorCodes.FILE_NOT_FOUND_ERROR) @boto3_mocking.patching def test_load_federal_report(self): """Tests the get_current_federal api endpoint""" - mock_client = MagicMock() - mock_client_instance = mock_client.return_value + with less_console_noise(): + mock_client = MagicMock() + mock_client_instance = mock_client.return_value - with open("registrar/tests/data/fake_current_federal.csv", "r") as file: - file_content = file.read() + with open("registrar/tests/data/fake_current_federal.csv", "r") as file: + file_content = file.read() - # Mock a recieved file - mock_client_instance.get_object.return_value = {"Body": io.BytesIO(file_content.encode())} - with boto3_mocking.clients.handler_for("s3", mock_client): - request = self.factory.get("/fake-path") - response = get_current_federal(request) + # Mock a recieved file + mock_client_instance.get_object.return_value = {"Body": io.BytesIO(file_content.encode())} + with boto3_mocking.clients.handler_for("s3", mock_client): + request = self.factory.get("/fake-path") + response = get_current_federal(request) - # Check that we are sending the correct calls. - # Ensures that we are decoding the file content recieved from AWS. - expected_call = [call.get_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key="current-federal.csv")] - mock_client_instance.assert_has_calls(expected_call) + # Check that we are sending the correct calls. + # Ensures that we are decoding the file content recieved from AWS. + expected_call = [call.get_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key="current-federal.csv")] + mock_client_instance.assert_has_calls(expected_call) - # Check that the response has status code 200 - self.assertEqual(response.status_code, 200) + # Check that the response has status code 200 + self.assertEqual(response.status_code, 200) - # Check that the response contains what we expect - expected_file_content = ( - "Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n" - "cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,\n" - "ddomain3.gov,Federal,Armed Forces Retirement Home,,,," - ).encode() + # Check that the response contains what we expect + expected_file_content = ( + "Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n" + "cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home,,,," + ).encode() - self.assertEqual(expected_file_content, response.content) + self.assertEqual(expected_file_content, response.content) @boto3_mocking.patching def test_load_full_report(self): """Tests the current-federal api link""" - mock_client = MagicMock() - mock_client_instance = mock_client.return_value + with less_console_noise(): + mock_client = MagicMock() + mock_client_instance = mock_client.return_value - with open("registrar/tests/data/fake_current_full.csv", "r") as file: - file_content = file.read() + with open("registrar/tests/data/fake_current_full.csv", "r") as file: + file_content = file.read() - # Mock a recieved file - mock_client_instance.get_object.return_value = {"Body": io.BytesIO(file_content.encode())} - with boto3_mocking.clients.handler_for("s3", mock_client): - request = self.factory.get("/fake-path") - response = get_current_full(request) + # Mock a recieved file + mock_client_instance.get_object.return_value = {"Body": io.BytesIO(file_content.encode())} + with boto3_mocking.clients.handler_for("s3", mock_client): + request = self.factory.get("/fake-path") + response = get_current_full(request) - # Check that we are sending the correct calls. - # Ensures that we are decoding the file content recieved from AWS. - expected_call = [call.get_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key="current-full.csv")] - mock_client_instance.assert_has_calls(expected_call) + # Check that we are sending the correct calls. + # Ensures that we are decoding the file content recieved from AWS. + expected_call = [call.get_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key="current-full.csv")] + mock_client_instance.assert_has_calls(expected_call) - # Check that the response has status code 200 - self.assertEqual(response.status_code, 200) + # Check that the response has status code 200 + self.assertEqual(response.status_code, 200) - # Check that the response contains what we expect - expected_file_content = ( - "Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n" - "cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,\n" - "ddomain3.gov,Federal,Armed Forces Retirement Home,,,,\n" - "adomain2.gov,Interstate,,,,," - ).encode() + # Check that the response contains what we expect + expected_file_content = ( + "Domain name,Domain type,Agency,Organization name,City,State,Security contact email\n" + "cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,,\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home,,,,\n" + "adomain2.gov,Interstate,,,,," + ).encode() - self.assertEqual(expected_file_content, response.content) + self.assertEqual(expected_file_content, response.content) class ExportDataTest(MockEppLib): @@ -339,192 +346,170 @@ class ExportDataTest(MockEppLib): def test_export_domains_to_writer_security_emails(self): """Test that export_domains_to_writer returns the expected security email""" - - # Add security email information - self.domain_1.name = "defaultsecurity.gov" - self.domain_1.save() - - # Invoke setter - self.domain_1.security_contact - - # Invoke setter - self.domain_2.security_contact - - # Invoke setter - self.domain_3.security_contact - - # Create a CSV file in memory - csv_file = StringIO() - writer = csv.writer(csv_file) - - # Define columns, sort fields, and filter condition - columns = [ - "Domain name", - "Domain type", - "Agency", - "Organization name", - "City", - "State", - "AO", - "AO email", - "Security contact email", - "Status", - "Expiration date", - ] - sort_fields = ["domain__name"] - filter_condition = { - "domain__state__in": [ - Domain.State.READY, - Domain.State.DNS_NEEDED, - Domain.State.ON_HOLD, - ], - } - - self.maxDiff = None - # Call the export functions - write_header(writer, columns) - write_body(writer, columns, sort_fields, filter_condition) - - # Reset the CSV file's position to the beginning - csv_file.seek(0) - - # Read the content into a variable - csv_content = csv_file.read() - - # We expect READY domains, - # sorted alphabetially by domain name - expected_content = ( - "Domain name,Domain type,Agency,Organization name,City,State,AO," - "AO email,Security contact email,Status,Expiration date\n" - "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" - "adomain2.gov,Interstate,(blank),Dns needed\n" - "ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n" - "defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready" - ) - - # Normalize line endings and remove commas, - # spaces and leading/trailing whitespace - csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() - expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() - - self.assertEqual(csv_content, expected_content) + with less_console_noise(): + # Add security email information + self.domain_1.name = "defaultsecurity.gov" + self.domain_1.save() + # Invoke setter + self.domain_1.security_contact + # Invoke setter + self.domain_2.security_contact + # Invoke setter + self.domain_3.security_contact + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) + # Define columns, sort fields, and filter condition + columns = [ + "Domain name", + "Domain type", + "Agency", + "Organization name", + "City", + "State", + "AO", + "AO email", + "Security contact email", + "Status", + "Expiration date", + ] + sort_fields = ["domain__name"] + filter_condition = { + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], + } + self.maxDiff = None + # Call the export functions + write_header(writer, columns) + write_body(writer, columns, sort_fields, filter_condition) + # Reset the CSV file's position to the beginning + csv_file.seek(0) + # Read the content into a variable + csv_content = csv_file.read() + # We expect READY domains, + # sorted alphabetially by domain name + expected_content = ( + "Domain name,Domain type,Agency,Organization name,City,State,AO," + "AO email,Security contact email,Status,Expiration date\n" + "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" + "adomain2.gov,Interstate,(blank),Dns needed\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n" + "defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready" + ) + # Normalize line endings and remove commas, + # spaces and leading/trailing whitespace + csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() + expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() + self.assertEqual(csv_content, expected_content) def test_write_body(self): """Test that write_body returns the existing domain, test that sort by domain name works, test that filter works""" - # Create a CSV file in memory - csv_file = StringIO() - writer = csv.writer(csv_file) + with less_console_noise(): + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) - # Define columns, sort fields, and filter condition - columns = [ - "Domain name", - "Domain type", - "Agency", - "Organization name", - "City", - "State", - "AO", - "AO email", - "Submitter", - "Submitter title", - "Submitter email", - "Submitter phone", - "Security contact email", - "Status", - ] - sort_fields = ["domain__name"] - filter_condition = { - "domain__state__in": [ - Domain.State.READY, - Domain.State.DNS_NEEDED, - Domain.State.ON_HOLD, - ], - } - - # Call the export functions - write_header(writer, columns) - write_body(writer, columns, sort_fields, filter_condition) - - # Reset the CSV file's position to the beginning - csv_file.seek(0) - - # Read the content into a variable - csv_content = csv_file.read() - - # We expect READY domains, - # sorted alphabetially by domain name - expected_content = ( - "Domain name,Domain type,Agency,Organization name,City,State,AO," - "AO email,Submitter,Submitter title,Submitter email,Submitter phone," - "Security contact email,Status\n" - "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" - "adomain2.gov,Interstate,Dns needed\n" - "cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n" - "ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\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.assertEqual(csv_content, expected_content) + # Define columns, sort fields, and filter condition + columns = [ + "Domain name", + "Domain type", + "Agency", + "Organization name", + "City", + "State", + "AO", + "AO email", + "Submitter", + "Submitter title", + "Submitter email", + "Submitter phone", + "Security contact email", + "Status", + ] + sort_fields = ["domain__name"] + filter_condition = { + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], + } + # Call the export functions + write_header(writer, columns) + write_body(writer, columns, sort_fields, filter_condition) + # Reset the CSV file's position to the beginning + csv_file.seek(0) + # Read the content into a variable + csv_content = csv_file.read() + # We expect READY domains, + # sorted alphabetially by domain name + expected_content = ( + "Domain name,Domain type,Agency,Organization name,City,State,AO," + "AO email,Submitter,Submitter title,Submitter email,Submitter phone," + "Security contact email,Status\n" + "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" + "adomain2.gov,Interstate,Dns needed\n" + "cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\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.assertEqual(csv_content, expected_content) def test_write_body_additional(self): """An additional test for filters and multi-column sort""" - # Create a CSV file in memory - csv_file = StringIO() - writer = csv.writer(csv_file) - - # Define columns, sort fields, and filter condition - columns = [ - "Domain name", - "Domain type", - "Agency", - "Organization name", - "City", - "State", - "Security contact email", - ] - sort_fields = ["domain__name", "federal_agency", "organization_type"] - filter_condition = { - "organization_type__icontains": "federal", - "domain__state__in": [ - Domain.State.READY, - Domain.State.DNS_NEEDED, - Domain.State.ON_HOLD, - ], - } - - # Call the export functions - write_header(writer, columns) - write_body(writer, columns, sort_fields, filter_condition) - - # Reset the CSV file's position to the beginning - csv_file.seek(0) - - # Read the content into a variable - csv_content = csv_file.read() - - # We expect READY domains, - # federal only - # sorted alphabetially by domain name - expected_content = ( - "Domain name,Domain type,Agency,Organization name,City," - "State,Security contact email\n" - "adomain10.gov,Federal,Armed Forces Retirement Home\n" - "cdomain1.gov,Federal - Executive,World War I Centennial Commission\n" - "ddomain3.gov,Federal,Armed Forces Retirement Home\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.assertEqual(csv_content, expected_content) + with less_console_noise(): + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) + # Define columns, sort fields, and filter condition + columns = [ + "Domain name", + "Domain type", + "Agency", + "Organization name", + "City", + "State", + "Security contact email", + ] + sort_fields = ["domain__name", "federal_agency", "organization_type"] + filter_condition = { + "organization_type__icontains": "federal", + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], + } + # Call the export functions + write_header(writer, columns) + write_body(writer, columns, sort_fields, filter_condition) + # Reset the CSV file's position to the beginning + csv_file.seek(0) + # Read the content into a variable + csv_content = csv_file.read() + # We expect READY domains, + # federal only + # sorted alphabetially by domain name + expected_content = ( + "Domain name,Domain type,Agency,Organization name,City," + "State,Security contact email\n" + "adomain10.gov,Federal,Armed Forces Retirement Home\n" + "cdomain1.gov,Federal - Executive,World War I Centennial Commission\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home\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.assertEqual(csv_content, expected_content) def test_write_body_with_date_filter_pulls_domains_in_range(self): """Test that domains that are @@ -538,88 +523,88 @@ class ExportDataTest(MockEppLib): which are hard to mock. TODO: Simplify is created_at is not needed for the report.""" + with less_console_noise(): + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) + # 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()). + end_date = timezone.make_aware(datetime.combine(date.today() + timedelta(days=2), datetime.min.time())) + start_date = timezone.make_aware(datetime.combine(date.today() - timedelta(days=2), datetime.min.time())) - # Create a CSV file in memory - csv_file = StringIO() - writer = csv.writer(csv_file) - # 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()). - end_date = timezone.make_aware(datetime.combine(date.today() + timedelta(days=2), datetime.min.time())) - start_date = timezone.make_aware(datetime.combine(date.today() - timedelta(days=2), datetime.min.time())) + # Define columns, sort fields, and filter condition + columns = [ + "Domain name", + "Domain type", + "Agency", + "Organization name", + "City", + "State", + "Status", + "Expiration date", + ] + sort_fields = [ + "created_at", + "domain__name", + ] + sort_fields_for_deleted_domains = [ + "domain__deleted", + "domain__name", + ] + filter_condition = { + "domain__state__in": [ + Domain.State.READY, + ], + "domain__first_ready__lte": end_date, + "domain__first_ready__gte": start_date, + } + filter_conditions_for_deleted_domains = { + "domain__state__in": [ + Domain.State.DELETED, + ], + "domain__deleted__lte": end_date, + "domain__deleted__gte": start_date, + } - # Define columns, sort fields, and filter condition - columns = [ - "Domain name", - "Domain type", - "Agency", - "Organization name", - "City", - "State", - "Status", - "Expiration date", - ] - sort_fields = [ - "created_at", - "domain__name", - ] - sort_fields_for_deleted_domains = [ - "domain__deleted", - "domain__name", - ] - filter_condition = { - "domain__state__in": [ - Domain.State.READY, - ], - "domain__first_ready__lte": end_date, - "domain__first_ready__gte": start_date, - } - filter_conditions_for_deleted_domains = { - "domain__state__in": [ - Domain.State.DELETED, - ], - "domain__deleted__lte": end_date, - "domain__deleted__gte": start_date, - } + # Call the export functions + write_header(writer, columns) + write_body( + writer, + columns, + sort_fields, + filter_condition, + ) + write_body( + writer, + columns, + sort_fields_for_deleted_domains, + filter_conditions_for_deleted_domains, + ) - # Call the export functions - write_header(writer, columns) - write_body( - writer, - columns, - sort_fields, - filter_condition, - ) - write_body( - writer, - columns, - sort_fields_for_deleted_domains, - filter_conditions_for_deleted_domains, - ) + # Reset the CSV file's position to the beginning + csv_file.seek(0) - # Reset the CSV file's position to the beginning - csv_file.seek(0) + # Read the content into a variable + csv_content = csv_file.read() - # Read the content into a variable - csv_content = csv_file.read() + # We expect READY domains first, created between today-2 and today+2, sorted by created_at then name + # and DELETED domains deleted between today-2 and today+2, sorted by deleted then name + expected_content = ( + "Domain name,Domain type,Agency,Organization name,City," + "State,Status,Expiration date\n" + "cdomain1.gov,Federal-Executive,World War I Centennial Commission,,,,Ready,\n" + "adomain10.gov,Federal,Armed Forces Retirement Home,,,,Ready,\n" + "zdomain9.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" + "sdomain8.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" + "xdomain7.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" + ) - # We expect READY domains first, created between today-2 and today+2, sorted by created_at then name - # and DELETED domains deleted between today-2 and today+2, sorted by deleted then name - expected_content = ( - "Domain name,Domain type,Agency,Organization name,City," - "State,Status,Expiration date\n" - "cdomain1.gov,Federal-Executive,World War I Centennial Commission,,,,Ready,\n" - "adomain10.gov,Federal,Armed Forces Retirement Home,,,,Ready,\n" - "zdomain9.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" - "sdomain8.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" - "xdomain7.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\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() - # Normalize line endings and remove commas, - # spaces and leading/trailing whitespace - csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() - expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() - - self.assertEqual(csv_content, expected_content) + self.assertEqual(csv_content, expected_content) class HelperFunctions(TestCase): diff --git a/src/registrar/tests/test_transition_domain_migrations.py b/src/registrar/tests/test_transition_domain_migrations.py index be4619e0b..e9453bd03 100644 --- a/src/registrar/tests/test_transition_domain_migrations.py +++ b/src/registrar/tests/test_transition_domain_migrations.py @@ -20,6 +20,9 @@ from registrar.models.contact import Contact from .common import MockSESClient, less_console_noise import boto3_mocking # type: ignore +import logging + +logger = logging.getLogger(__name__) class TestProcessedMigrations(TestCase): @@ -55,17 +58,18 @@ class TestProcessedMigrations(TestCase): The 'call_command' function from Django's management framework is then used to execute the load_transition_domain command with the specified arguments. """ - # noqa here because splitting this up makes it confusing. - # ES501 - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command( - "load_transition_domain", - self.migration_json_filename, - directory=self.test_data_file_location, - ) + with less_console_noise(): + # noqa here because splitting this up makes it confusing. + # ES501 + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command( + "load_transition_domain", + self.migration_json_filename, + directory=self.test_data_file_location, + ) def run_transfer_domains(self): """ @@ -74,101 +78,104 @@ class TestProcessedMigrations(TestCase): The 'call_command' function from Django's management framework is then used to execute the load_transition_domain command with the specified arguments. """ - call_command("transfer_transition_domains_to_domains") + with less_console_noise(): + call_command("transfer_transition_domains_to_domains") def test_domain_idempotent(self): """ This test ensures that the domain transfer process is idempotent on Domain and DomainInformation. """ - unchanged_domain, _ = Domain.objects.get_or_create( - name="testdomain.gov", - state=Domain.State.READY, - expiration_date=datetime.date(2000, 1, 1), - ) - unchanged_domain_information, _ = DomainInformation.objects.get_or_create( - domain=unchanged_domain, organization_name="test org name", creator=self.user - ) - self.run_load_domains() + with less_console_noise(): + unchanged_domain, _ = Domain.objects.get_or_create( + name="testdomain.gov", + state=Domain.State.READY, + expiration_date=datetime.date(2000, 1, 1), + ) + unchanged_domain_information, _ = DomainInformation.objects.get_or_create( + domain=unchanged_domain, organization_name="test org name", creator=self.user + ) + self.run_load_domains() - # Test that a given TransitionDomain isn't set to "processed" - transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") - self.assertFalse(transition_domain_object.processed) + # Test that a given TransitionDomain isn't set to "processed" + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertFalse(transition_domain_object.processed) - self.run_transfer_domains() + self.run_transfer_domains() - # Test that old data isn't corrupted - actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() - actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() - self.assertEqual(unchanged_domain, actual_unchanged) - self.assertEqual(unchanged_domain_information, actual_unchanged_information) + # Test that old data isn't corrupted + actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() + actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() + self.assertEqual(unchanged_domain, actual_unchanged) + self.assertEqual(unchanged_domain_information, actual_unchanged_information) - # Test that a given TransitionDomain is set to "processed" after we transfer domains - transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") - self.assertTrue(transition_domain_object.processed) + # Test that a given TransitionDomain is set to "processed" after we transfer domains + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertTrue(transition_domain_object.processed) - # Manually change Domain/DomainInformation objects - changed_domain = Domain.objects.filter(name="fakewebsite3.gov").get() - changed_domain.expiration_date = datetime.date(1999, 1, 1) + # Manually change Domain/DomainInformation objects + changed_domain = Domain.objects.filter(name="fakewebsite3.gov").get() + changed_domain.expiration_date = datetime.date(1999, 1, 1) - changed_domain.save() + changed_domain.save() - changed_domain_information = DomainInformation.objects.filter(domain=changed_domain).get() - changed_domain_information.organization_name = "changed" + changed_domain_information = DomainInformation.objects.filter(domain=changed_domain).get() + changed_domain_information.organization_name = "changed" - changed_domain_information.save() + changed_domain_information.save() - # Rerun transfer domains - self.run_transfer_domains() + # Rerun transfer domains + self.run_transfer_domains() - # Test that old data isn't corrupted after running this twice - actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() - actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() - self.assertEqual(unchanged_domain, actual_unchanged) - self.assertEqual(unchanged_domain_information, actual_unchanged_information) + # Test that old data isn't corrupted after running this twice + actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() + actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() + self.assertEqual(unchanged_domain, actual_unchanged) + self.assertEqual(unchanged_domain_information, actual_unchanged_information) - # Ensure that domain hasn't changed - actual_domain = Domain.objects.filter(name="fakewebsite3.gov").get() - self.assertEqual(changed_domain, actual_domain) + # Ensure that domain hasn't changed + actual_domain = Domain.objects.filter(name="fakewebsite3.gov").get() + self.assertEqual(changed_domain, actual_domain) - # Ensure that DomainInformation hasn't changed - actual_domain_information = DomainInformation.objects.filter(domain=changed_domain).get() - self.assertEqual(changed_domain_information, actual_domain_information) + # Ensure that DomainInformation hasn't changed + actual_domain_information = DomainInformation.objects.filter(domain=changed_domain).get() + self.assertEqual(changed_domain_information, actual_domain_information) def test_transition_domain_is_processed(self): """ This test checks if a domain is correctly marked as processed in the transition. """ - old_transition_domain, _ = TransitionDomain.objects.get_or_create(domain_name="testdomain.gov") - # Asser that old records default to 'True' - self.assertTrue(old_transition_domain.processed) + with less_console_noise(): + old_transition_domain, _ = TransitionDomain.objects.get_or_create(domain_name="testdomain.gov") + # Asser that old records default to 'True' + self.assertTrue(old_transition_domain.processed) - unchanged_domain, _ = Domain.objects.get_or_create( - name="testdomain.gov", - state=Domain.State.READY, - expiration_date=datetime.date(2000, 1, 1), - ) - unchanged_domain_information, _ = DomainInformation.objects.get_or_create( - domain=unchanged_domain, organization_name="test org name", creator=self.user - ) - self.run_load_domains() + unchanged_domain, _ = Domain.objects.get_or_create( + name="testdomain.gov", + state=Domain.State.READY, + expiration_date=datetime.date(2000, 1, 1), + ) + unchanged_domain_information, _ = DomainInformation.objects.get_or_create( + domain=unchanged_domain, organization_name="test org name", creator=self.user + ) + self.run_load_domains() - # Test that a given TransitionDomain isn't set to "processed" - transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") - self.assertFalse(transition_domain_object.processed) + # Test that a given TransitionDomain isn't set to "processed" + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertFalse(transition_domain_object.processed) - self.run_transfer_domains() + self.run_transfer_domains() - # Test that old data isn't corrupted - actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() - actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() - self.assertEqual(unchanged_domain, actual_unchanged) - self.assertTrue(old_transition_domain.processed) - self.assertEqual(unchanged_domain_information, actual_unchanged_information) + # Test that old data isn't corrupted + actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() + actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() + self.assertEqual(unchanged_domain, actual_unchanged) + self.assertTrue(old_transition_domain.processed) + self.assertEqual(unchanged_domain_information, actual_unchanged_information) - # Test that a given TransitionDomain is set to "processed" after we transfer domains - transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") - self.assertTrue(transition_domain_object.processed) + # Test that a given TransitionDomain is set to "processed" after we transfer domains + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertTrue(transition_domain_object.processed) class TestOrganizationMigration(TestCase): @@ -200,17 +207,18 @@ class TestOrganizationMigration(TestCase): The 'call_command' function from Django's management framework is then used to execute the load_transition_domain command with the specified arguments. """ - # noqa here because splitting this up makes it confusing. - # ES501 - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command( - "load_transition_domain", - self.migration_json_filename, - directory=self.test_data_file_location, - ) + with less_console_noise(): + # noqa here because splitting this up makes it confusing. + # ES501 + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command( + "load_transition_domain", + self.migration_json_filename, + directory=self.test_data_file_location, + ) def run_transfer_domains(self): """ @@ -219,7 +227,8 @@ class TestOrganizationMigration(TestCase): The 'call_command' function from Django's management framework is then used to execute the load_transition_domain command with the specified arguments. """ - call_command("transfer_transition_domains_to_domains") + with less_console_noise(): + call_command("transfer_transition_domains_to_domains") def run_load_organization_data(self): """ @@ -232,17 +241,18 @@ class TestOrganizationMigration(TestCase): The 'call_command' function from Django's management framework is then used to execute the load_organization_data command with the specified arguments. """ - # noqa here (E501) because splitting this up makes it - # confusing to read. - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command( - "load_organization_data", - self.migration_json_filename, - directory=self.test_data_file_location, - ) + with less_console_noise(): + # noqa here (E501) because splitting this up makes it + # confusing to read. + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command( + "load_organization_data", + self.migration_json_filename, + directory=self.test_data_file_location, + ) def compare_tables( self, @@ -256,7 +266,6 @@ class TestOrganizationMigration(TestCase): """Does a diff between the transition_domain and the following tables: domain, domain_information and the domain_invitation. Verifies that the data loaded correctly.""" - missing_domains = [] duplicate_domains = [] missing_domain_informations = [] @@ -300,59 +309,64 @@ class TestOrganizationMigration(TestCase): 3. Checks that the data has been loaded as expected. The expected result is a set of TransitionDomain objects with specific attributes. - The test fetches the actual TransitionDomain objects from the database and compares them with the expected objects. - """ # noqa - E501 (harder to read) - # == First, parse all existing data == # - self.run_load_domains() - self.run_transfer_domains() + The test fetches the actual TransitionDomain objects from the database and compares them with + the expected objects. + """ + with less_console_noise(): + # noqa - E501 (harder to read) + # == First, parse all existing data == # + self.run_load_domains() + self.run_transfer_domains() - # == Second, try adding org data to it == # - self.run_load_organization_data() + # == Second, try adding org data to it == # + self.run_load_organization_data() - # == Third, test that we've loaded data as we expect == # - transition_domains = TransitionDomain.objects.filter(domain_name="fakewebsite2.gov") + # == Third, test that we've loaded data as we expect == # + transition_domains = TransitionDomain.objects.filter(domain_name="fakewebsite2.gov") - # Should return three objects (three unique emails) - self.assertEqual(transition_domains.count(), 3) + # Should return three objects (three unique emails) + self.assertEqual(transition_domains.count(), 3) - # Lets test the first one - transition = transition_domains.first() - expected_transition_domain = TransitionDomain( - username="alexandra.bobbitt5@test.com", - domain_name="fakewebsite2.gov", - status="on hold", - email_sent=True, - organization_type="Federal", - organization_name="Fanoodle", - federal_type="Executive", - federal_agency="Department of Commerce", - epp_creation_date=datetime.date(2004, 5, 7), - epp_expiration_date=datetime.date(2023, 9, 30), - first_name="Seline", - middle_name="testmiddle2", - last_name="Tower", - title=None, - email="stower3@answers.com", - phone="151-539-6028", - address_line="93001 Arizona Drive", - city="Columbus", - state_territory="Oh", - zipcode="43268", - ) - expected_transition_domain.id = transition.id + # Lets test the first one + transition = transition_domains.first() + expected_transition_domain = TransitionDomain( + username="alexandra.bobbitt5@test.com", + domain_name="fakewebsite2.gov", + status="on hold", + email_sent=True, + organization_type="Federal", + organization_name="Fanoodle", + federal_type="Executive", + federal_agency="Department of Commerce", + epp_creation_date=datetime.date(2004, 5, 7), + epp_expiration_date=datetime.date(2023, 9, 30), + first_name="Seline", + middle_name="testmiddle2", + last_name="Tower", + title=None, + email="stower3@answers.com", + phone="151-539-6028", + address_line="93001 Arizona Drive", + city="Columbus", + state_territory="Oh", + zipcode="43268", + ) + expected_transition_domain.id = transition.id - self.assertEqual(transition, expected_transition_domain) + self.assertEqual(transition, expected_transition_domain) def test_transition_domain_status_unknown(self): """ Test that a domain in unknown status can be loaded - """ # noqa - E501 (harder to read) - # == First, parse all existing data == # - self.run_load_domains() - self.run_transfer_domains() + """ + with less_console_noise(): + # noqa - E501 (harder to read) + # == First, parse all existing data == # + self.run_load_domains() + self.run_transfer_domains() - domain_object = Domain.objects.get(name="fakewebsite3.gov") - self.assertEqual(domain_object.state, Domain.State.UNKNOWN) + domain_object = Domain.objects.get(name="fakewebsite3.gov") + self.assertEqual(domain_object.state, Domain.State.UNKNOWN) def test_load_organization_data_domain_information(self): """ @@ -367,35 +381,38 @@ class TestOrganizationMigration(TestCase): The test fetches the actual DomainInformation object from the database and compares it with the expected object. """ - # == First, parse all existing data == # - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + # == First, parse all existing data == # + self.run_load_domains() + self.run_transfer_domains() - # == Second, try adding org data to it == # - self.run_load_organization_data() + # == Second, try adding org data to it == # + self.run_load_organization_data() - # == Third, test that we've loaded data as we expect == # - _domain = Domain.objects.filter(name="fakewebsite2.gov").get() - domain_information = DomainInformation.objects.filter(domain=_domain).get() + # == Third, test that we've loaded data as we expect == # + _domain = Domain.objects.filter(name="fakewebsite2.gov").get() + domain_information = DomainInformation.objects.filter(domain=_domain).get() - expected_creator = User.objects.filter(username="System").get() - expected_ao = Contact.objects.filter(first_name="Seline", middle_name="testmiddle2", last_name="Tower").get() - expected_domain_information = DomainInformation( - creator=expected_creator, - organization_type="federal", - federal_agency="Department of Commerce", - federal_type="executive", - organization_name="Fanoodle", - address_line1="93001 Arizona Drive", - city="Columbus", - state_territory="Oh", - zipcode="43268", - authorizing_official=expected_ao, - domain=_domain, - ) - # Given that these are different objects, this needs to be set - expected_domain_information.id = domain_information.id - self.assertEqual(domain_information, expected_domain_information) + expected_creator = User.objects.filter(username="System").get() + expected_ao = Contact.objects.filter( + first_name="Seline", middle_name="testmiddle2", last_name="Tower" + ).get() + expected_domain_information = DomainInformation( + creator=expected_creator, + organization_type="federal", + federal_agency="Department of Commerce", + federal_type="executive", + organization_name="Fanoodle", + address_line1="93001 Arizona Drive", + city="Columbus", + state_territory="Oh", + zipcode="43268", + authorizing_official=expected_ao, + domain=_domain, + ) + # Given that these are different objects, this needs to be set + expected_domain_information.id = domain_information.id + self.assertEqual(domain_information, expected_domain_information) def test_load_organization_data_preserves_existing_data(self): """ @@ -410,44 +427,47 @@ class TestOrganizationMigration(TestCase): The expected result is that the DomainInformation object retains its pre-existing data after the load_organization_data method is run. """ - # == First, parse all existing data == # - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + # == First, parse all existing data == # + self.run_load_domains() + self.run_transfer_domains() - # == Second, try add prexisting fake data == # - _domain_old = Domain.objects.filter(name="fakewebsite2.gov").get() - domain_information_old = DomainInformation.objects.filter(domain=_domain_old).get() - domain_information_old.address_line1 = "93001 Galactic Way" - domain_information_old.city = "Olympus" - domain_information_old.state_territory = "MA" - domain_information_old.zipcode = "12345" - domain_information_old.save() + # == Second, try add prexisting fake data == # + _domain_old = Domain.objects.filter(name="fakewebsite2.gov").get() + domain_information_old = DomainInformation.objects.filter(domain=_domain_old).get() + domain_information_old.address_line1 = "93001 Galactic Way" + domain_information_old.city = "Olympus" + domain_information_old.state_territory = "MA" + domain_information_old.zipcode = "12345" + domain_information_old.save() - # == Third, try running the script == # - self.run_load_organization_data() + # == Third, try running the script == # + self.run_load_organization_data() - # == Fourth, test that no data is overwritten as we expect == # - _domain = Domain.objects.filter(name="fakewebsite2.gov").get() - domain_information = DomainInformation.objects.filter(domain=_domain).get() + # == Fourth, test that no data is overwritten as we expect == # + _domain = Domain.objects.filter(name="fakewebsite2.gov").get() + domain_information = DomainInformation.objects.filter(domain=_domain).get() - expected_creator = User.objects.filter(username="System").get() - expected_ao = Contact.objects.filter(first_name="Seline", middle_name="testmiddle2", last_name="Tower").get() - expected_domain_information = DomainInformation( - creator=expected_creator, - organization_type="federal", - federal_agency="Department of Commerce", - federal_type="executive", - organization_name="Fanoodle", - address_line1="93001 Galactic Way", - city="Olympus", - state_territory="MA", - zipcode="12345", - authorizing_official=expected_ao, - domain=_domain, - ) - # Given that these are different objects, this needs to be set - expected_domain_information.id = domain_information.id - self.assertEqual(domain_information, expected_domain_information) + expected_creator = User.objects.filter(username="System").get() + expected_ao = Contact.objects.filter( + first_name="Seline", middle_name="testmiddle2", last_name="Tower" + ).get() + expected_domain_information = DomainInformation( + creator=expected_creator, + organization_type="federal", + federal_agency="Department of Commerce", + federal_type="executive", + organization_name="Fanoodle", + address_line1="93001 Galactic Way", + city="Olympus", + state_territory="MA", + zipcode="12345", + authorizing_official=expected_ao, + domain=_domain, + ) + # Given that these are different objects, this needs to be set + expected_domain_information.id = domain_information.id + self.assertEqual(domain_information, expected_domain_information) def test_load_organization_data_integrity(self): """ @@ -462,29 +482,30 @@ class TestOrganizationMigration(TestCase): The expected result is that the counts of objects in the database match the expected counts, indicating that the data has not been corrupted. """ - # First, parse all existing data - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + # First, parse all existing data + self.run_load_domains() + self.run_transfer_domains() - # Second, try adding org data to it - self.run_load_organization_data() + # Second, try adding org data to it + self.run_load_organization_data() - # Third, test that we didn't corrupt any data - expected_total_transition_domains = 9 - expected_total_domains = 5 - expected_total_domain_informations = 5 + # Third, test that we didn't corrupt any data + expected_total_transition_domains = 9 + expected_total_domains = 5 + expected_total_domain_informations = 5 - expected_missing_domains = 0 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 0 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - ) + expected_missing_domains = 0 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 0 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + ) class TestMigrations(TestCase): @@ -521,39 +542,42 @@ class TestMigrations(TestCase): UserDomainRole.objects.all().delete() def run_load_domains(self): - # noqa here because splitting this up makes it confusing. - # ES501 - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command( - "load_transition_domain", - self.migration_json_filename, - directory=self.test_data_file_location, - ) - - def run_transfer_domains(self): - call_command("transfer_transition_domains_to_domains") - - def run_master_script(self): - # noqa here (E501) because splitting this up makes it - # confusing to read. - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + # noqa here because splitting this up makes it confusing. + # ES501 with patch( "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa return_value=True, ): - with patch("registrar.utility.email.send_templated_email", return_value=None): - call_command( - "master_domain_migrations", - runMigrations=True, - migrationDirectory=self.test_data_file_location, - migrationJSON=self.migration_json_filename, - disablePrompts=True, - ) - print(f"here: {mock_client.EMAILS_SENT}") + call_command( + "load_transition_domain", + self.migration_json_filename, + directory=self.test_data_file_location, + ) + + def run_transfer_domains(self): + with less_console_noise(): + call_command("transfer_transition_domains_to_domains") + + def run_master_script(self): + with less_console_noise(): + # noqa here (E501) because splitting this up makes it + # confusing to read. + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + with patch("registrar.utility.email.send_templated_email", return_value=None): + call_command( + "master_domain_migrations", + runMigrations=True, + migrationDirectory=self.test_data_file_location, + migrationJSON=self.migration_json_filename, + disablePrompts=True, + ) + logger.debug(f"here: {mock_client.EMAILS_SENT}") def compare_tables( self, @@ -607,7 +631,7 @@ class TestMigrations(TestCase): total_domain_informations = len(DomainInformation.objects.all()) total_domain_invitations = len(DomainInvitation.objects.all()) - print( + logger.debug( f""" total_missing_domains = {len(missing_domains)} total_duplicate_domains = {len(duplicate_domains)} @@ -636,225 +660,230 @@ class TestMigrations(TestCase): follow best practice of limiting the number of assertions per test. But for now, this will double-check that the script works as intended.""" + with less_console_noise(): + self.run_master_script() - self.run_master_script() + # STEP 2: (analyze the tables just like the + # migration script does, but add assert statements) + expected_total_transition_domains = 9 + expected_total_domains = 5 + expected_total_domain_informations = 5 + expected_total_domain_invitations = 8 - # STEP 2: (analyze the tables just like the - # migration script does, but add assert statements) - expected_total_transition_domains = 9 - expected_total_domains = 5 - expected_total_domain_informations = 5 - expected_total_domain_invitations = 8 - - expected_missing_domains = 0 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 0 - # we expect 1 missing invite from anomaly.gov (an injected error) - expected_missing_domain_invitations = 1 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_total_domain_invitations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - expected_missing_domain_invitations, - ) + expected_missing_domains = 0 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 0 + # we expect 1 missing invite from anomaly.gov (an injected error) + expected_missing_domain_invitations = 1 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_total_domain_invitations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + expected_missing_domain_invitations, + ) def test_load_empty_transition_domain(self): """Loads TransitionDomains without additional data""" - self.run_load_domains() + with less_console_noise(): + self.run_load_domains() - # STEP 2: (analyze the tables just like the migration - # script does, but add assert statements) - expected_total_transition_domains = 9 - expected_total_domains = 0 - expected_total_domain_informations = 0 - expected_total_domain_invitations = 0 + # STEP 2: (analyze the tables just like the migration + # script does, but add assert statements) + expected_total_transition_domains = 9 + expected_total_domains = 0 + expected_total_domain_informations = 0 + expected_total_domain_invitations = 0 - expected_missing_domains = 9 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 9 - expected_missing_domain_invitations = 9 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_total_domain_invitations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - expected_missing_domain_invitations, - ) + expected_missing_domains = 9 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 9 + expected_missing_domain_invitations = 9 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_total_domain_invitations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + expected_missing_domain_invitations, + ) def test_load_full_domain(self): - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + self.run_load_domains() + self.run_transfer_domains() - # Analyze the tables - expected_total_transition_domains = 9 - expected_total_domains = 5 - expected_total_domain_informations = 5 - expected_total_domain_invitations = 8 + # Analyze the tables + expected_total_transition_domains = 9 + expected_total_domains = 5 + expected_total_domain_informations = 5 + expected_total_domain_invitations = 8 - expected_missing_domains = 0 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 0 - expected_missing_domain_invitations = 1 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_total_domain_invitations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - expected_missing_domain_invitations, - ) + expected_missing_domains = 0 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 0 + expected_missing_domain_invitations = 1 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_total_domain_invitations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + expected_missing_domain_invitations, + ) - # Test created domains - anomaly_domains = Domain.objects.filter(name="anomaly.gov") - self.assertEqual(anomaly_domains.count(), 1) - anomaly = anomaly_domains.get() + # Test created domains + anomaly_domains = Domain.objects.filter(name="anomaly.gov") + self.assertEqual(anomaly_domains.count(), 1) + anomaly = anomaly_domains.get() - self.assertEqual(anomaly.expiration_date, datetime.date(2023, 3, 9)) + self.assertEqual(anomaly.expiration_date, datetime.date(2023, 3, 9)) - self.assertEqual(anomaly.name, "anomaly.gov") - self.assertEqual(anomaly.state, "ready") + self.assertEqual(anomaly.name, "anomaly.gov") + self.assertEqual(anomaly.state, "ready") - testdomain_domains = Domain.objects.filter(name="fakewebsite2.gov") - self.assertEqual(testdomain_domains.count(), 1) + testdomain_domains = Domain.objects.filter(name="fakewebsite2.gov") + self.assertEqual(testdomain_domains.count(), 1) - testdomain = testdomain_domains.get() + testdomain = testdomain_domains.get() - self.assertEqual(testdomain.expiration_date, datetime.date(2023, 9, 30)) - self.assertEqual(testdomain.name, "fakewebsite2.gov") - self.assertEqual(testdomain.state, "on hold") + self.assertEqual(testdomain.expiration_date, datetime.date(2023, 9, 30)) + self.assertEqual(testdomain.name, "fakewebsite2.gov") + self.assertEqual(testdomain.state, "on hold") def test_load_full_domain_information(self): - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + self.run_load_domains() + self.run_transfer_domains() - # Analyze the tables - expected_total_transition_domains = 9 - expected_total_domains = 5 - expected_total_domain_informations = 5 - expected_total_domain_invitations = 8 + # Analyze the tables + expected_total_transition_domains = 9 + expected_total_domains = 5 + expected_total_domain_informations = 5 + expected_total_domain_invitations = 8 - expected_missing_domains = 0 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 0 - expected_missing_domain_invitations = 1 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_total_domain_invitations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - expected_missing_domain_invitations, - ) + expected_missing_domains = 0 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 0 + expected_missing_domain_invitations = 1 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_total_domain_invitations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + expected_missing_domain_invitations, + ) - # Test created Domain Information objects - domain = Domain.objects.filter(name="anomaly.gov").get() - anomaly_domain_infos = DomainInformation.objects.filter(domain=domain) + # Test created Domain Information objects + domain = Domain.objects.filter(name="anomaly.gov").get() + anomaly_domain_infos = DomainInformation.objects.filter(domain=domain) - self.assertEqual(anomaly_domain_infos.count(), 1) + self.assertEqual(anomaly_domain_infos.count(), 1) - # This domain should be pretty barebones. Something isnt - # parsing right if we get a lot of data. - anomaly = anomaly_domain_infos.get() - self.assertEqual(anomaly.organization_name, "Flashdog") - self.assertEqual(anomaly.organization_type, None) - self.assertEqual(anomaly.federal_agency, None) - self.assertEqual(anomaly.federal_type, None) + # This domain should be pretty barebones. Something isnt + # parsing right if we get a lot of data. + anomaly = anomaly_domain_infos.get() + self.assertEqual(anomaly.organization_name, "Flashdog") + self.assertEqual(anomaly.organization_type, None) + self.assertEqual(anomaly.federal_agency, None) + self.assertEqual(anomaly.federal_type, None) - # Check for the "system" creator user - Users = User.objects.filter(username="System") - self.assertEqual(Users.count(), 1) - self.assertEqual(anomaly.creator, Users.get()) + # Check for the "system" creator user + Users = User.objects.filter(username="System") + self.assertEqual(Users.count(), 1) + self.assertEqual(anomaly.creator, Users.get()) - domain = Domain.objects.filter(name="fakewebsite2.gov").get() - fakewebsite_domain_infos = DomainInformation.objects.filter(domain=domain) - self.assertEqual(fakewebsite_domain_infos.count(), 1) + domain = Domain.objects.filter(name="fakewebsite2.gov").get() + fakewebsite_domain_infos = DomainInformation.objects.filter(domain=domain) + self.assertEqual(fakewebsite_domain_infos.count(), 1) - fakewebsite = fakewebsite_domain_infos.get() - self.assertEqual(fakewebsite.organization_name, "Fanoodle") - self.assertEqual(fakewebsite.organization_type, "federal") - self.assertEqual(fakewebsite.federal_agency, "Department of Commerce") - self.assertEqual(fakewebsite.federal_type, "executive") + fakewebsite = fakewebsite_domain_infos.get() + self.assertEqual(fakewebsite.organization_name, "Fanoodle") + self.assertEqual(fakewebsite.organization_type, "federal") + self.assertEqual(fakewebsite.federal_agency, "Department of Commerce") + self.assertEqual(fakewebsite.federal_type, "executive") - ao = fakewebsite.authorizing_official + ao = fakewebsite.authorizing_official - self.assertEqual(ao.first_name, "Seline") - self.assertEqual(ao.middle_name, "testmiddle2") - self.assertEqual(ao.last_name, "Tower") - self.assertEqual(ao.email, "stower3@answers.com") - self.assertEqual(ao.phone, "151-539-6028") + self.assertEqual(ao.first_name, "Seline") + self.assertEqual(ao.middle_name, "testmiddle2") + self.assertEqual(ao.last_name, "Tower") + self.assertEqual(ao.email, "stower3@answers.com") + self.assertEqual(ao.phone, "151-539-6028") - # Check for the "system" creator user - Users = User.objects.filter(username="System") - self.assertEqual(Users.count(), 1) - self.assertEqual(anomaly.creator, Users.get()) + # Check for the "system" creator user + Users = User.objects.filter(username="System") + self.assertEqual(Users.count(), 1) + self.assertEqual(anomaly.creator, Users.get()) def test_transfer_transition_domains_to_domains(self): - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + self.run_load_domains() + self.run_transfer_domains() - # Analyze the tables - expected_total_transition_domains = 9 - expected_total_domains = 5 - expected_total_domain_informations = 5 - expected_total_domain_invitations = 8 + # Analyze the tables + expected_total_transition_domains = 9 + expected_total_domains = 5 + expected_total_domain_informations = 5 + expected_total_domain_invitations = 8 - expected_missing_domains = 0 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 0 - expected_missing_domain_invitations = 1 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_total_domain_invitations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - expected_missing_domain_invitations, - ) + expected_missing_domains = 0 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 0 + expected_missing_domain_invitations = 1 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_total_domain_invitations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + expected_missing_domain_invitations, + ) def test_logins(self): - # TODO: setup manually instead of calling other scripts - self.run_load_domains() - self.run_transfer_domains() + with less_console_noise(): + # TODO: setup manually instead of calling other scripts + self.run_load_domains() + self.run_transfer_domains() - # Simluate Logins - for invite in DomainInvitation.objects.all(): - # get a user with this email address - user, user_created = User.objects.get_or_create(email=invite.email, username=invite.email) - user.on_each_login() + # Simluate Logins + for invite in DomainInvitation.objects.all(): + # get a user with this email address + user, user_created = User.objects.get_or_create(email=invite.email, username=invite.email) + user.on_each_login() - # Analyze the tables - expected_total_transition_domains = 9 - expected_total_domains = 5 - expected_total_domain_informations = 5 - expected_total_domain_invitations = 8 + # Analyze the tables + expected_total_transition_domains = 9 + expected_total_domains = 5 + expected_total_domain_informations = 5 + expected_total_domain_invitations = 8 - expected_missing_domains = 0 - expected_duplicate_domains = 0 - expected_missing_domain_informations = 0 - expected_missing_domain_invitations = 1 - self.compare_tables( - expected_total_transition_domains, - expected_total_domains, - expected_total_domain_informations, - expected_total_domain_invitations, - expected_missing_domains, - expected_duplicate_domains, - expected_missing_domain_informations, - expected_missing_domain_invitations, - ) + expected_missing_domains = 0 + expected_duplicate_domains = 0 + expected_missing_domain_informations = 0 + expected_missing_domain_invitations = 1 + self.compare_tables( + expected_total_transition_domains, + expected_total_domains, + expected_total_domain_informations, + expected_total_domain_invitations, + expected_missing_domains, + expected_duplicate_domains, + expected_missing_domain_informations, + expected_missing_domain_invitations, + ) @boto3_mocking.patching def test_send_domain_invitations_email(self): diff --git a/src/registrar/tests/test_url_auth.py b/src/registrar/tests/test_url_auth.py index 34f80ac44..3e0514a85 100644 --- a/src/registrar/tests/test_url_auth.py +++ b/src/registrar/tests/test_url_auth.py @@ -23,6 +23,7 @@ SAMPLE_KWARGS = { "content_type_id": "2", "object_id": "3", "domain": "whitehouse.gov", + "user_pk": "1", } # Our test suite will ignore some namespaces. diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index f8373710c..450993e5c 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1,44 +1,17 @@ -from unittest import skip -from unittest.mock import MagicMock, ANY, patch - -from django.conf import settings from django.test import Client, TestCase from django.urls import reverse from django.contrib.auth import get_user_model -from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore -from django_webtest import WebTest # type: ignore -import boto3_mocking # type: ignore +from .common import MockEppLib # type: ignore -from registrar.utility.errors import ( - NameserverError, - NameserverErrorCodes, - SecurityEmailError, - SecurityEmailErrorCodes, - GenericError, - GenericErrorCodes, - DsDataError, - DsDataErrorCodes, -) from registrar.models import ( DomainApplication, - Domain, DomainInformation, DraftDomain, - DomainInvitation, Contact, - PublicContact, - Host, - HostIP, - Website, - UserDomainRole, User, ) -from registrar.views.application import ApplicationWizard, Step -from datetime import date, datetime, timedelta -from django.utils import timezone - from .common import less_console_noise import logging @@ -159,32 +132,33 @@ class LoggedInTests(TestWithUser): # Given that we are including a subset of items that can be deleted while excluding the rest, # subTest is appropriate here as otherwise we would need many duplicate tests for the same reason. - draft_domain = DraftDomain.objects.create(name="igorville.gov") - for status in DomainApplication.ApplicationStatus: - if status not in [ - DomainApplication.ApplicationStatus.STARTED, - DomainApplication.ApplicationStatus.WITHDRAWN, - ]: - with self.subTest(status=status): - application = DomainApplication.objects.create( - creator=self.user, requested_domain=draft_domain, status=status - ) + with less_console_noise(): + draft_domain = DraftDomain.objects.create(name="igorville.gov") + for status in DomainApplication.ApplicationStatus: + if status not in [ + DomainApplication.ApplicationStatus.STARTED, + DomainApplication.ApplicationStatus.WITHDRAWN, + ]: + with self.subTest(status=status): + application = DomainApplication.objects.create( + creator=self.user, requested_domain=draft_domain, status=status + ) - # Trigger the delete logic - response = self.client.post( - reverse("application-delete", kwargs={"pk": application.pk}), follow=True - ) + # Trigger the delete logic + response = self.client.post( + reverse("application-delete", kwargs={"pk": application.pk}), follow=True + ) - # Check for a 403 error - the end user should not be allowed to do this - self.assertEqual(response.status_code, 403) + # Check for a 403 error - the end user should not be allowed to do this + self.assertEqual(response.status_code, 403) - desired_application = DomainApplication.objects.filter(requested_domain=draft_domain) + desired_application = DomainApplication.objects.filter(requested_domain=draft_domain) - # Make sure the DomainApplication wasn't deleted - self.assertEqual(desired_application.count(), 1) + # Make sure the DomainApplication wasn't deleted + self.assertEqual(desired_application.count(), 1) - # clean up - application.delete() + # clean up + application.delete() def test_home_deletes_domain_application_and_orphans(self): """Tests if delete for DomainApplication deletes orphaned Contact objects""" @@ -329,3569 +303,4 @@ class LoggedInTests(TestWithUser): with less_console_noise(): response = self.client.get("/request/", follow=True) - print(response.status_code) self.assertEqual(response.status_code, 403) - - -class DomainApplicationTests(TestWithUser, WebTest): - - """Webtests for domain application to test filling and submitting.""" - - # Doesn't work with CSRF checking - # hypothesis is that CSRF_USE_SESSIONS is incompatible with WebTest - csrf_checks = False - - def setUp(self): - super().setUp() - self.app.set_user(self.user.username) - self.TITLES = ApplicationWizard.TITLES - - def test_application_form_intro_acknowledgement(self): - """Tests that user is presented with intro acknowledgement page""" - intro_page = self.app.get(reverse("application:")) - self.assertContains(intro_page, "You’re about to start your .gov domain request") - - def test_application_form_intro_is_skipped_when_edit_access(self): - """Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'""" - completed_application(status=DomainApplication.ApplicationStatus.STARTED, user=self.user) - home_page = self.app.get("/") - self.assertContains(home_page, "city.gov") - # click the "Edit" link - detail_page = home_page.click("Edit", index=0) - # Check that the response is a redirect - self.assertEqual(detail_page.status_code, 302) - # You can access the 'Location' header to get the redirect URL - redirect_url = detail_page.url - self.assertEqual(redirect_url, "/request/organization_type/") - - def test_application_form_empty_submit(self): - """Tests empty submit on the first page after the acknowledgement page""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - # submitting should get back the same page if the required field is empty - result = type_page.forms[0].submit() - self.assertIn("What kind of U.S.-based government organization do you represent?", result) - - def test_application_multiple_applications_exist(self): - """Test that an info message appears when user has multiple applications already""" - # create and submit an application - application = completed_application(user=self.user) - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - application.submit() - application.save() - - # now, attempt to create another one - with less_console_noise(): - intro_page = self.app.get(reverse("application:")) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - self.assertContains(type_page, "You cannot submit this request yet") - - @boto3_mocking.patching - def test_application_form_submission(self): - """ - Can fill out the entire form and submit. - As we add additional form pages, we need to include them here to make - this test work. - - This test also looks for the long organization name on the summary page. - - This also tests for the presence of a modal trigger and the dynamic test - in the modal header on the submit page. - """ - num_pages_tested = 0 - # elections, type_of_work, tribal_government - SKIPPED_PAGES = 3 - num_pages = len(self.TITLES) - SKIPPED_PAGES - - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - # ---- TYPE PAGE ---- - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = "federal" - # test next button and validate data - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - # should see results in db - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.organization_type, "federal") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(type_result.status_code, 302) - self.assertEqual(type_result["Location"], "/request/organization_federal/") - num_pages_tested += 1 - - # ---- FEDERAL BRANCH PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - federal_page = type_result.follow() - federal_form = federal_page.forms[0] - federal_form["organization_federal-federal_type"] = "executive" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_result = federal_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.federal_type, "executive") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(federal_result.status_code, 302) - self.assertEqual(federal_result["Location"], "/request/organization_contact/") - num_pages_tested += 1 - - # ---- ORG CONTACT PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - org_contact_page = federal_result.follow() - 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"] = "General Services Administration" - org_contact_form["organization_contact-organization_name"] = "Testorg" - org_contact_form["organization_contact-address_line1"] = "address 1" - org_contact_form["organization_contact-address_line2"] = "address 2" - org_contact_form["organization_contact-city"] = "NYC" - org_contact_form["organization_contact-state_territory"] = "NY" - org_contact_form["organization_contact-zipcode"] = "10002" - org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - org_contact_result = org_contact_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.organization_name, "Testorg") - self.assertEqual(application.address_line1, "address 1") - self.assertEqual(application.address_line2, "address 2") - self.assertEqual(application.city, "NYC") - self.assertEqual(application.state_territory, "NY") - self.assertEqual(application.zipcode, "10002") - self.assertEqual(application.urbanization, "URB Royal Oaks") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(org_contact_result.status_code, 302) - self.assertEqual(org_contact_result["Location"], "/request/authorizing_official/") - num_pages_tested += 1 - - # ---- AUTHORIZING OFFICIAL PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_page = org_contact_result.follow() - ao_form = ao_page.forms[0] - ao_form["authorizing_official-first_name"] = "Testy ATO" - ao_form["authorizing_official-last_name"] = "Tester ATO" - ao_form["authorizing_official-title"] = "Chief Tester" - ao_form["authorizing_official-email"] = "testy@town.com" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_result = ao_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.authorizing_official.first_name, "Testy ATO") - self.assertEqual(application.authorizing_official.last_name, "Tester ATO") - self.assertEqual(application.authorizing_official.title, "Chief Tester") - self.assertEqual(application.authorizing_official.email, "testy@town.com") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(ao_result.status_code, 302) - self.assertEqual(ao_result["Location"], "/request/current_sites/") - num_pages_tested += 1 - - # ---- CURRENT SITES PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_page = ao_result.follow() - current_sites_form = current_sites_page.forms[0] - current_sites_form["current_sites-0-website"] = "www.city.com" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_result = current_sites_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual( - application.current_websites.filter(website="http://www.city.com").count(), - 1, - ) - # the post request should return a redirect to the next form in - # the application - self.assertEqual(current_sites_result.status_code, 302) - self.assertEqual(current_sites_result["Location"], "/request/dotgov_domain/") - num_pages_tested += 1 - - # ---- DOTGOV DOMAIN PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - dotgov_page = current_sites_result.follow() - dotgov_form = dotgov_page.forms[0] - dotgov_form["dotgov_domain-requested_domain"] = "city" - dotgov_form["dotgov_domain-0-alternative_domain"] = "city1" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - dotgov_result = dotgov_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.requested_domain.name, "city.gov") - self.assertEqual(application.alternative_domains.filter(website="city1.gov").count(), 1) - # the post request should return a redirect to the next form in - # the application - self.assertEqual(dotgov_result.status_code, 302) - self.assertEqual(dotgov_result["Location"], "/request/purpose/") - num_pages_tested += 1 - - # ---- PURPOSE PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - purpose_page = dotgov_result.follow() - purpose_form = purpose_page.forms[0] - purpose_form["purpose-purpose"] = "For all kinds of things." - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - purpose_result = purpose_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.purpose, "For all kinds of things.") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(purpose_result.status_code, 302) - self.assertEqual(purpose_result["Location"], "/request/your_contact/") - num_pages_tested += 1 - - # ---- YOUR CONTACT INFO PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - your_contact_page = purpose_result.follow() - your_contact_form = your_contact_page.forms[0] - - your_contact_form["your_contact-first_name"] = "Testy you" - your_contact_form["your_contact-last_name"] = "Tester you" - your_contact_form["your_contact-title"] = "Admin Tester" - your_contact_form["your_contact-email"] = "testy-admin@town.com" - your_contact_form["your_contact-phone"] = "(201) 555 5556" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - your_contact_result = your_contact_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.submitter.first_name, "Testy you") - self.assertEqual(application.submitter.last_name, "Tester you") - self.assertEqual(application.submitter.title, "Admin Tester") - self.assertEqual(application.submitter.email, "testy-admin@town.com") - self.assertEqual(application.submitter.phone, "(201) 555 5556") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(your_contact_result.status_code, 302) - self.assertEqual(your_contact_result["Location"], "/request/other_contacts/") - num_pages_tested += 1 - - # ---- OTHER CONTACTS PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - other_contacts_page = your_contact_result.follow() - - # This page has 3 forms in 1. - # Let's set the yes/no radios to enable the other contacts fieldsets - other_contacts_form = other_contacts_page.forms[0] - - other_contacts_form["other_contacts-has_other_contacts"] = "True" - - other_contacts_form["other_contacts-0-first_name"] = "Testy2" - other_contacts_form["other_contacts-0-last_name"] = "Tester2" - other_contacts_form["other_contacts-0-title"] = "Another Tester" - other_contacts_form["other_contacts-0-email"] = "testy2@town.com" - other_contacts_form["other_contacts-0-phone"] = "(201) 555 5557" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - other_contacts_result = other_contacts_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual( - application.other_contacts.filter( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(201) 555 5557", - ).count(), - 1, - ) - # the post request should return a redirect to the next form in - # the application - self.assertEqual(other_contacts_result.status_code, 302) - self.assertEqual(other_contacts_result["Location"], "/request/anything_else/") - num_pages_tested += 1 - - # ---- ANYTHING ELSE PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - anything_else_page = other_contacts_result.follow() - anything_else_form = anything_else_page.forms[0] - - anything_else_form["anything_else-anything_else"] = "Nothing else." - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - anything_else_result = anything_else_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.anything_else, "Nothing else.") - # the post request should return a redirect to the next form in - # the application - self.assertEqual(anything_else_result.status_code, 302) - self.assertEqual(anything_else_result["Location"], "/request/requirements/") - num_pages_tested += 1 - - # ---- REQUIREMENTS PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - requirements_page = anything_else_result.follow() - requirements_form = requirements_page.forms[0] - - requirements_form["requirements-is_policy_acknowledged"] = True - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - requirements_result = requirements_form.submit() - # validate that data from this step are being saved - application = DomainApplication.objects.get() # there's only one - self.assertEqual(application.is_policy_acknowledged, True) - # the post request should return a redirect to the next form in - # the application - self.assertEqual(requirements_result.status_code, 302) - self.assertEqual(requirements_result["Location"], "/request/review/") - num_pages_tested += 1 - - # ---- REVIEW AND FINSIHED PAGES ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - review_page = requirements_result.follow() - review_form = review_page.forms[0] - - # Review page contains all the previously entered data - # Let's make sure the long org name is displayed - self.assertContains(review_page, "Federal") - self.assertContains(review_page, "Executive") - self.assertContains(review_page, "Testorg") - self.assertContains(review_page, "address 1") - self.assertContains(review_page, "address 2") - self.assertContains(review_page, "NYC") - self.assertContains(review_page, "NY") - self.assertContains(review_page, "10002") - self.assertContains(review_page, "URB Royal Oaks") - self.assertContains(review_page, "Testy ATO") - self.assertContains(review_page, "Tester ATO") - self.assertContains(review_page, "Chief Tester") - self.assertContains(review_page, "testy@town.com") - self.assertContains(review_page, "city.com") - self.assertContains(review_page, "city.gov") - self.assertContains(review_page, "city1.gov") - self.assertContains(review_page, "For all kinds of things.") - self.assertContains(review_page, "Testy you") - self.assertContains(review_page, "Tester you") - self.assertContains(review_page, "Admin Tester") - self.assertContains(review_page, "testy-admin@town.com") - self.assertContains(review_page, "(201) 555-5556") - self.assertContains(review_page, "Testy2") - self.assertContains(review_page, "Tester2") - self.assertContains(review_page, "Another Tester") - self.assertContains(review_page, "testy2@town.com") - self.assertContains(review_page, "(201) 555-5557") - self.assertContains(review_page, "Nothing else.") - - # We can't test the modal itself as it relies on JS for init and triggering, - # but we can test for the existence of its trigger: - self.assertContains(review_page, "toggle-submit-domain-request") - # And the existence of the modal's data parked and ready for the js init. - # The next assert also tests for the passed requested domain context from - # the view > application_form > modal - self.assertContains(review_page, "You are about to submit a domain request for city.gov") - - # final submission results in a redirect to the "finished" URL - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - with less_console_noise(): - review_result = review_form.submit() - - self.assertEqual(review_result.status_code, 302) - self.assertEqual(review_result["Location"], "/request/finished/") - num_pages_tested += 1 - - # following this redirect is a GET request, so include the cookie - # here too. - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - with less_console_noise(): - final_result = review_result.follow() - self.assertContains(final_result, "Thanks for your domain request!") - - # check that any new pages are added to this test - self.assertEqual(num_pages, num_pages_tested) - - # This is the start of a test to check an existing application, it currently - # does not work and results in errors as noted in: - # https://github.com/cisagov/getgov/pull/728 - @skip("WIP") - def test_application_form_started_allsteps(self): - num_pages_tested = 0 - # elections, type_of_work, tribal_government - SKIPPED_PAGES = 3 - DASHBOARD_PAGE = 1 - num_pages = len(self.TITLES) - SKIPPED_PAGES + DASHBOARD_PAGE - - application = completed_application(user=self.user) - application.save() - home_page = self.app.get("/") - self.assertContains(home_page, "city.gov") - self.assertContains(home_page, "Started") - num_pages_tested += 1 - - # TODO: For some reason this click results in a new application being generated - # This appraoch is an alternatie to using get as is being done below - # - # type_page = home_page.click("Edit") - - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - url = reverse("edit-application", kwargs={"id": application.pk}) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - # TODO: The following line results in a django error on middleware - response = self.client.get(url, follow=True) - self.assertContains(response, "Type of organization") - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # TODO: Step through the remaining pages - - self.assertEqual(num_pages, num_pages_tested) - - def test_application_form_conditional_federal(self): - """Federal branch question is shown for federal organizations.""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - # ---- TYPE PAGE ---- - - # the conditional step titles shouldn't appear initially - self.assertNotContains(type_page, self.TITLES["organization_federal"]) - self.assertNotContains(type_page, self.TITLES["organization_election"]) - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = "federal" - - # set the session ID before .submit() - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - - # the post request should return a redirect to the federal branch - # question - self.assertEqual(type_result.status_code, 302) - self.assertEqual(type_result["Location"], "/request/organization_federal/") - - # and the step label should appear in the sidebar of the resulting page - # but the step label for the elections page should not appear - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_page = type_result.follow() - self.assertContains(federal_page, self.TITLES["organization_federal"]) - self.assertNotContains(federal_page, self.TITLES["organization_election"]) - - # continuing on in the flow we need to see top-level agency on the - # contact page - federal_page.forms[0]["organization_federal-federal_type"] = "executive" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_result = federal_page.forms[0].submit() - # the post request should return a redirect to the contact - # question - self.assertEqual(federal_result.status_code, 302) - self.assertEqual(federal_result["Location"], "/request/organization_contact/") - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - contact_page = federal_result.follow() - self.assertContains(contact_page, "Federal agency") - - def test_application_form_conditional_elections(self): - """Election question is shown for other organizations.""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - # ---- TYPE PAGE ---- - - # the conditional step titles shouldn't appear initially - self.assertNotContains(type_page, self.TITLES["organization_federal"]) - self.assertNotContains(type_page, self.TITLES["organization_election"]) - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = "county" - - # set the session ID before .submit() - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - - # the post request should return a redirect to the elections question - self.assertEqual(type_result.status_code, 302) - self.assertEqual(type_result["Location"], "/request/organization_election/") - - # and the step label should appear in the sidebar of the resulting page - # but the step label for the elections page should not appear - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - election_page = type_result.follow() - self.assertContains(election_page, self.TITLES["organization_election"]) - self.assertNotContains(election_page, self.TITLES["organization_federal"]) - - # continuing on in the flow we need to NOT see top-level agency on the - # contact page - election_page.forms[0]["organization_election-is_election_board"] = "True" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - election_result = election_page.forms[0].submit() - # the post request should return a redirect to the contact - # question - self.assertEqual(election_result.status_code, 302) - self.assertEqual(election_result["Location"], "/request/organization_contact/") - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - contact_page = election_result.follow() - self.assertNotContains(contact_page, "Federal agency") - - def test_application_form_section_skipping(self): - """Can skip forward and back in sections""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = "federal" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_page = type_result.follow() - - # Now on federal type page, click back to the organization type - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - new_page = federal_page.click(str(self.TITLES["organization_type"]), index=0) - - # Should be a link to the organization_federal page - self.assertGreater( - len(new_page.html.find_all("a", href="/request/organization_federal/")), - 0, - ) - - def test_application_form_nonfederal(self): - """Non-federal organizations don't have to provide their federal agency.""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - contact_page = type_result.follow() - org_contact_form = contact_page.forms[0] - - self.assertNotIn("federal_agency", org_contact_form.fields) - - # minimal fields that must be filled out - org_contact_form["organization_contact-organization_name"] = "Testorg" - org_contact_form["organization_contact-address_line1"] = "address 1" - org_contact_form["organization_contact-city"] = "NYC" - org_contact_form["organization_contact-state_territory"] = "NY" - org_contact_form["organization_contact-zipcode"] = "10002" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - contact_result = org_contact_form.submit() - - # the post request should return a redirect to the - # about your organization page if it was successful. - self.assertEqual(contact_result.status_code, 302) - self.assertEqual(contact_result["Location"], "/request/about_your_organization/") - - def test_application_about_your_organization_special(self): - """Special districts have to answer an additional question.""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.forms[0].submit() - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - contact_page = type_result.follow() - - self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) - - def test_yes_no_form_inits_blank_for_new_application(self): - """On the Other Contacts page, the yes/no form gets initialized with nothing selected for - new applications""" - other_contacts_page = self.app.get(reverse("application:other_contacts")) - other_contacts_form = other_contacts_page.forms[0] - self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, None) - - def test_yes_no_form_inits_yes_for_application_with_other_contacts(self): - """On the Other Contacts page, the yes/no form gets initialized with YES selected if the - application has other contacts""" - # Application has other contacts by default - application = completed_application(user=self.user) - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") - - def test_yes_no_form_inits_no_for_application_with_no_other_contacts_rationale(self): - """On the Other Contacts page, the yes/no form gets initialized with NO selected if the - application has no other contacts""" - # Application has other contacts by default - application = completed_application(user=self.user, has_other_contacts=False) - application.no_other_contacts_rationale = "Hello!" - application.save() - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False") - - def test_submitting_other_contacts_deletes_no_other_contacts_rationale(self): - """When a user submits the Other Contacts form with other contacts selected, the application's - no other contacts rationale gets deleted""" - # Application has other contacts by default - application = completed_application(user=self.user, has_other_contacts=False) - application.no_other_contacts_rationale = "Hello!" - application.save() - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False") - - other_contacts_form["other_contacts-has_other_contacts"] = "True" - - other_contacts_form["other_contacts-0-first_name"] = "Testy" - other_contacts_form["other_contacts-0-middle_name"] = "" - other_contacts_form["other_contacts-0-last_name"] = "McTesterson" - other_contacts_form["other_contacts-0-title"] = "Lord" - other_contacts_form["other_contacts-0-email"] = "testy@abc.org" - other_contacts_form["other_contacts-0-phone"] = "(201) 555-0123" - - # Submit the now empty form - other_contacts_form.submit() - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - # Verify that the no_other_contacts_rationale we saved earlier has been removed from the database - application = DomainApplication.objects.get() - self.assertEqual( - application.other_contacts.count(), - 1, - ) - - self.assertEquals( - application.no_other_contacts_rationale, - None, - ) - - 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 application's - other contacts get deleted for other contacts that exist and are not joined to other objects - """ - # Application has other contacts by default - application = completed_application(user=self.user) - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") - - other_contacts_form["other_contacts-has_other_contacts"] = "False" - - other_contacts_form["other_contacts-no_other_contacts_rationale"] = "Hello again!" - - # Submit the now empty form - other_contacts_form.submit() - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - # Verify that the no_other_contacts_rationale we saved earlier has been removed from the database - application = DomainApplication.objects.get() - self.assertEqual( - application.other_contacts.count(), - 0, - ) - - self.assertEquals( - application.no_other_contacts_rationale, - "Hello again!", - ) - - 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 application's - other contacts references get removed for other contacts that exist and are joined to other objects""" - # Populate the database with a domain application that - # has 1 "other contact" assigned to it - # We'll do it from scratch so we can reuse the other contact - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(555) 555 5555", - ) - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(555) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(555) 555 5557", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - submitter=you, - creator=self.user, - status="started", - ) - application.other_contacts.add(other) - - # Now let's join the other contact to another object - domain_info = DomainInformation.objects.create(creator=self.user) - domain_info.other_contacts.set([other]) - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") - - other_contacts_form["other_contacts-has_other_contacts"] = "False" - - other_contacts_form["other_contacts-no_other_contacts_rationale"] = "Hello again!" - - # Submit the now empty form - other_contacts_form.submit() - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - # Verify that the no_other_contacts_rationale we saved earlier is no longer associated with the application - application = DomainApplication.objects.get() - self.assertEqual( - application.other_contacts.count(), - 0, - ) - - # Verify that the 'other' contact object still exists - domain_info = DomainInformation.objects.get() - self.assertEqual( - domain_info.other_contacts.count(), - 1, - ) - self.assertEqual( - domain_info.other_contacts.all()[0].first_name, - "Testy2", - ) - - self.assertEquals( - application.no_other_contacts_rationale, - "Hello again!", - ) - - def test_if_yes_no_form_is_no_then_no_other_contacts_required(self): - """Applicants with no other contacts have to give a reason.""" - other_contacts_page = self.app.get(reverse("application:other_contacts")) - other_contacts_form = other_contacts_page.forms[0] - other_contacts_form["other_contacts-has_other_contacts"] = "False" - response = other_contacts_page.forms[0].submit() - - # The textarea for no other contacts returns this error message - # Assert that it is returned, ie the no other contacts form is required - self.assertContains(response, "Rationale for no other employees is required.") - - # The first name field for other contacts returns this error message - # 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.") - - def test_if_yes_no_form_is_yes_then_other_contacts_required(self): - """Applicants with other contacts do not have to give a reason.""" - other_contacts_page = self.app.get(reverse("application:other_contacts")) - other_contacts_form = other_contacts_page.forms[0] - other_contacts_form["other_contacts-has_other_contacts"] = "True" - response = other_contacts_page.forms[0].submit() - - # The textarea for no other contacts returns this error message - # Assert that it is not returned, ie the no other contacts form is not required - self.assertNotContains(response, "Rationale for no other employees is required.") - - # The first name field for other contacts returns this error message - # Assert that it is returned, ie the contacts form is required - self.assertContains(response, "Enter the first name / given name of this contact.") - - def test_delete_other_contact(self): - """Other contacts can be deleted after being saved to database. - - This formset uses the DJANGO DELETE widget. We'll test that by setting 2 contacts on an application, - loading the form and marking one contact up for deletion.""" - # Populate the database with a domain application that - # has 2 "other contact" assigned to it - # We'll do it from scratch so we can reuse the other contact - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(201) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(201) 555 5557", - ) - other2, _ = Contact.objects.get_or_create( - first_name="Testy3", - last_name="Tester3", - title="Another Tester", - email="testy3@town.com", - phone="(201) 555 5557", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - submitter=you, - creator=self.user, - status="started", - ) - application.other_contacts.add(other) - application.other_contacts.add(other2) - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - - # Minimal check to ensure the form is loaded with both other contacts - self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") - self.assertEqual(other_contacts_form["other_contacts-1-first_name"].value, "Testy3") - - # Mark the first dude for deletion - other_contacts_form.set("other_contacts-0-DELETE", "on") - - # Submit the form - other_contacts_form.submit() - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - # Verify that the first dude was deleted - application = DomainApplication.objects.get() - self.assertEqual(application.other_contacts.count(), 1) - self.assertEqual(application.other_contacts.first().first_name, "Testy3") - - def test_delete_other_contact_does_not_allow_zero_contacts(self): - """Delete Other Contact does not allow submission with zero contacts.""" - # Populate the database with a domain application that - # has 1 "other contact" assigned to it - # We'll do it from scratch so we can reuse the other contact - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(201) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(201) 555 5557", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - submitter=you, - creator=self.user, - status="started", - ) - application.other_contacts.add(other) - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") - - # Mark the first dude for deletion - other_contacts_form.set("other_contacts-0-DELETE", "on") - - # Submit the form - other_contacts_form.submit() - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - # Verify that the contact was not deleted - application = DomainApplication.objects.get() - self.assertEqual(application.other_contacts.count(), 1) - self.assertEqual(application.other_contacts.first().first_name, "Testy2") - - def test_delete_other_contact_sets_visible_empty_form_as_required_after_failed_submit(self): - """When you: - 1. add an empty contact, - 2. delete existing contacts, - 3. then submit, - The forms on page reload shows all the required fields and their errors.""" - - # Populate the database with a domain application that - # has 1 "other contact" assigned to it - # We'll do it from scratch so we can reuse the other contact - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(201) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(201) 555 5557", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - submitter=you, - creator=self.user, - status="started", - ) - application.other_contacts.add(other) - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") - - # Set total forms to 2 indicating an additional formset was added. - # Submit no data though for the second formset. - # Set the first formset to be deleted. - other_contacts_form["other_contacts-TOTAL_FORMS"] = "2" - other_contacts_form.set("other_contacts-0-DELETE", "on") - - response = other_contacts_form.submit() - - # Assert that the response presents errors to the user, including to - # Enter the first name ... - self.assertContains(response, "Enter the first name / given name of this contact.") - - def test_edit_other_contact_in_place(self): - """When you: - 1. edit an existing contact which is not joined to another model, - 2. then submit, - The application is linked to the existing contact, and the existing contact updated.""" - - # Populate the database with a domain application that - # has 1 "other contact" assigned to it - # We'll do it from scratch - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(201) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(201) 555 5557", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - submitter=you, - creator=self.user, - status="started", - ) - application.other_contacts.add(other) - - # other_contact_pk is the initial pk of the other contact. set it before update - # to be able to verify after update that the same contact object is in place - other_contact_pk = other.id - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") - - # update the first name of the contact - other_contacts_form["other_contacts-0-first_name"] = "Testy3" - - # Submit the updated form - other_contacts_form.submit() - - application.refresh_from_db() - - # assert that the Other Contact is updated "in place" - other_contact = application.other_contacts.all()[0] - self.assertEquals(other_contact_pk, other_contact.id) - self.assertEquals("Testy3", other_contact.first_name) - - def test_edit_other_contact_creates_new(self): - """When you: - 1. edit an existing contact which IS joined to another model, - 2. then submit, - The application is linked to a new contact, and the new contact is updated.""" - - # Populate the database with a domain application that - # has 1 "other contact" assigned to it, the other contact is also - # the authorizing official initially - # We'll do it from scratch - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(201) 555 5556", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - submitter=you, - creator=self.user, - status="started", - ) - application.other_contacts.add(ao) - - # other_contact_pk is the initial pk of the other contact. set it before update - # to be able to verify after update that the ao contact is still in place - # and not updated, and that the new contact has a new id - other_contact_pk = ao.id - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_page = self.app.get(reverse("application:other_contacts")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - other_contacts_form = other_contacts_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy") - - # update the first name of the contact - other_contacts_form["other_contacts-0-first_name"] = "Testy2" - - # Submit the updated form - other_contacts_form.submit() - - application.refresh_from_db() - - # assert that other contact info is updated, and that a new Contact - # is created for the other contact - other_contact = application.other_contacts.all()[0] - self.assertNotEquals(other_contact_pk, other_contact.id) - self.assertEquals("Testy2", other_contact.first_name) - # assert that the authorizing official is not updated - authorizing_official = application.authorizing_official - self.assertEquals("Testy", authorizing_official.first_name) - - def test_edit_authorizing_official_in_place(self): - """When you: - 1. edit an authorizing official which is not joined to another model, - 2. then submit, - The application is linked to the existing ao, and the ao updated.""" - - # Populate the database with a domain application that - # has an authorizing_official (ao) - # We'll do it from scratch - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - creator=self.user, - status="started", - ) - - # ao_pk is the initial pk of the Authorizing Official. set it before update - # to be able to verify after update that the same Contact object is in place - ao_pk = ao.id - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - ao_page = self.app.get(reverse("application:authorizing_official")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - ao_form = ao_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(ao_form["authorizing_official-first_name"].value, "Testy") - - # update the first name of the contact - ao_form["authorizing_official-first_name"] = "Testy2" - - # Submit the updated form - ao_form.submit() - - application.refresh_from_db() - - # assert AO is updated "in place" - updated_ao = application.authorizing_official - self.assertEquals(ao_pk, updated_ao.id) - self.assertEquals("Testy2", updated_ao.first_name) - - def test_edit_authorizing_official_creates_new(self): - """When you: - 1. edit an existing authorizing official which IS joined to another model, - 2. then submit, - The application is linked to a new Contact, and the new Contact is updated.""" - - # Populate the database with a domain application that - # has authorizing official assigned to it, the authorizing offical is also - # an other contact initially - # We'll do it from scratch - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - creator=self.user, - status="started", - ) - application.other_contacts.add(ao) - - # ao_pk is the initial pk of the authorizing official. set it before update - # to be able to verify after update that the other contact is still in place - # and not updated, and that the new ao has a new id - ao_pk = ao.id - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - ao_page = self.app.get(reverse("application:authorizing_official")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - ao_form = ao_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(ao_form["authorizing_official-first_name"].value, "Testy") - - # update the first name of the contact - ao_form["authorizing_official-first_name"] = "Testy2" - - # Submit the updated form - ao_form.submit() - - application.refresh_from_db() - - # assert that the other contact is not updated - other_contacts = application.other_contacts.all() - other_contact = other_contacts[0] - self.assertEquals(ao_pk, other_contact.id) - self.assertEquals("Testy", other_contact.first_name) - # assert that the authorizing official is updated - authorizing_official = application.authorizing_official - self.assertEquals("Testy2", authorizing_official.first_name) - - def test_edit_submitter_in_place(self): - """When you: - 1. edit a submitter (your contact) which is not joined to another model, - 2. then submit, - The application is linked to the existing submitter, and the submitter updated.""" - - # Populate the database with a domain application that - # has a submitter - # We'll do it from scratch - you, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - submitter=you, - creator=self.user, - status="started", - ) - - # submitter_pk is the initial pk of the submitter. set it before update - # to be able to verify after update that the same contact object is in place - submitter_pk = you.id - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - your_contact_page = self.app.get(reverse("application:your_contact")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - your_contact_form = your_contact_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(your_contact_form["your_contact-first_name"].value, "Testy") - - # update the first name of the contact - your_contact_form["your_contact-first_name"] = "Testy2" - - # Submit the updated form - your_contact_form.submit() - - application.refresh_from_db() - - updated_submitter = application.submitter - self.assertEquals(submitter_pk, updated_submitter.id) - self.assertEquals("Testy2", updated_submitter.first_name) - - def test_edit_submitter_creates_new(self): - """When you: - 1. edit an existing your contact which IS joined to another model, - 2. then submit, - The application is linked to a new Contact, and the new Contact is updated.""" - - # Populate the database with a domain application that - # has submitter assigned to it, the submitter is also - # an other contact initially - # We'll do it from scratch - submitter, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(201) 555 5555", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - submitter=submitter, - creator=self.user, - status="started", - ) - application.other_contacts.add(submitter) - - # submitter_pk is the initial pk of the your contact. set it before update - # to be able to verify after update that the other contact is still in place - # and not updated, and that the new submitter has a new id - submitter_pk = submitter.id - - # prime the form by visiting /edit - self.app.get(reverse("edit-application", kwargs={"id": application.pk})) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - your_contact_page = self.app.get(reverse("application:your_contact")) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - your_contact_form = your_contact_page.forms[0] - - # Minimal check to ensure the form is loaded - self.assertEqual(your_contact_form["your_contact-first_name"].value, "Testy") - - # update the first name of the contact - your_contact_form["your_contact-first_name"] = "Testy2" - - # Submit the updated form - your_contact_form.submit() - - application.refresh_from_db() - - # assert that the other contact is not updated - other_contacts = application.other_contacts.all() - other_contact = other_contacts[0] - self.assertEquals(submitter_pk, other_contact.id) - self.assertEquals("Testy", other_contact.first_name) - # assert that the submitter is updated - submitter = application.submitter - self.assertEquals("Testy2", submitter.first_name) - - def test_application_about_your_organiztion_interstate(self): - """Special districts have to answer an additional question.""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - contact_page = type_result.follow() - - self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) - - def test_application_tribal_government(self): - """Tribal organizations have to answer an additional question.""" - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - # the tribal government page comes immediately afterwards - self.assertIn("/tribal_government", type_result.headers["Location"]) - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - tribal_government_page = type_result.follow() - - # and the step is on the sidebar list. - self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT]) - - def test_application_ao_dynamic_text(self): - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - # ---- TYPE PAGE ---- - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = "federal" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - - # ---- FEDERAL BRANCH PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_page = type_result.follow() - federal_form = federal_page.forms[0] - federal_form["organization_federal-federal_type"] = "executive" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_result = federal_form.submit() - - # ---- ORG CONTACT PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - org_contact_page = federal_result.follow() - 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"] = "General Services Administration" - org_contact_form["organization_contact-organization_name"] = "Testorg" - org_contact_form["organization_contact-address_line1"] = "address 1" - org_contact_form["organization_contact-address_line2"] = "address 2" - org_contact_form["organization_contact-city"] = "NYC" - org_contact_form["organization_contact-state_territory"] = "NY" - org_contact_form["organization_contact-zipcode"] = "10002" - org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - org_contact_result = org_contact_form.submit() - - # ---- AO CONTACT PAGE ---- - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_page = org_contact_result.follow() - self.assertContains(ao_page, "Executive branch federal agencies") - - # Go back to organization type page and change type - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_page.click(str(self.TITLES["organization_type"]), index=0) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_form["organization_type-organization_type"] = "city" - type_result = type_form.submit() - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - election_page = type_result.follow() - - # Go back to AO page and test the dynamic text changed - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_page = election_page.click(str(self.TITLES["authorizing_official"]), index=0) - self.assertContains(ao_page, "Domain requests from cities") - - def test_application_dotgov_domain_dynamic_text(self): - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - # ---- TYPE PAGE ---- - type_form = type_page.forms[0] - type_form["organization_type-organization_type"] = "federal" - - # test next button - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_form.submit() - - # ---- FEDERAL BRANCH PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_page = type_result.follow() - federal_form = federal_page.forms[0] - federal_form["organization_federal-federal_type"] = "executive" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_result = federal_form.submit() - - # ---- ORG CONTACT PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - org_contact_page = federal_result.follow() - 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"] = "General Services Administration" - org_contact_form["organization_contact-organization_name"] = "Testorg" - org_contact_form["organization_contact-address_line1"] = "address 1" - org_contact_form["organization_contact-address_line2"] = "address 2" - org_contact_form["organization_contact-city"] = "NYC" - org_contact_form["organization_contact-state_territory"] = "NY" - org_contact_form["organization_contact-zipcode"] = "10002" - org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - org_contact_result = org_contact_form.submit() - - # ---- AO CONTACT PAGE ---- - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_page = org_contact_result.follow() - - # ---- AUTHORIZING OFFICIAL PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_page = org_contact_result.follow() - ao_form = ao_page.forms[0] - ao_form["authorizing_official-first_name"] = "Testy ATO" - ao_form["authorizing_official-last_name"] = "Tester ATO" - ao_form["authorizing_official-title"] = "Chief Tester" - ao_form["authorizing_official-email"] = "testy@town.com" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_result = ao_form.submit() - - # ---- CURRENT SITES PAGE ---- - # Follow the redirect to the next form page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_page = ao_result.follow() - current_sites_form = current_sites_page.forms[0] - current_sites_form["current_sites-0-website"] = "www.city.com" - - # test saving the page - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_result = current_sites_form.submit() - - # ---- DOTGOV DOMAIN PAGE ---- - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - dotgov_page = current_sites_result.follow() - - self.assertContains(dotgov_page, "medicare.gov") - - # Go back to organization type page and change type - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - dotgov_page.click(str(self.TITLES["organization_type"]), index=0) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_form["organization_type-organization_type"] = "city" - type_result = type_form.submit() - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - election_page = type_result.follow() - - # Go back to dotgov domain page to test the dynamic text changed - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - dotgov_page = election_page.click(str(self.TITLES["dotgov_domain"]), index=0) - self.assertContains(dotgov_page, "CityofEudoraKS.gov") - self.assertNotContains(dotgov_page, "medicare.gov") - - def test_application_formsets(self): - """Users are able to add more than one of some fields.""" - current_sites_page = self.app.get(reverse("application:current_sites")) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - # fill in the form field - current_sites_form = current_sites_page.forms[0] - self.assertIn("current_sites-0-website", current_sites_form.fields) - self.assertNotIn("current_sites-1-website", current_sites_form.fields) - current_sites_form["current_sites-0-website"] = "https://example.com" - - # click "Add another" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_result = current_sites_form.submit("submit_button", value="save") - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_form = current_sites_result.follow().forms[0] - - # verify that there are two form fields - value = current_sites_form["current_sites-0-website"].value - self.assertEqual(value, "https://example.com") - self.assertIn("current_sites-1-website", current_sites_form.fields) - # and it is correctly referenced in the ManyToOne relationship - application = DomainApplication.objects.get() # there's only one - self.assertEqual( - application.current_websites.filter(website="https://example.com").count(), - 1, - ) - - @skip("WIP") - def test_application_edit_restore(self): - """ - Test that a previously saved application is available at the /edit endpoint. - """ - ao, _ = Contact.objects.get_or_create( - first_name="Testy", - last_name="Tester", - title="Chief Tester", - email="testy@town.com", - phone="(555) 555 5555", - ) - domain, _ = Domain.objects.get_or_create(name="city.gov") - alt, _ = Website.objects.get_or_create(website="city1.gov") - current, _ = Website.objects.get_or_create(website="city.com") - you, _ = Contact.objects.get_or_create( - first_name="Testy you", - last_name="Tester you", - title="Admin Tester", - email="testy-admin@town.com", - phone="(555) 555 5556", - ) - other, _ = Contact.objects.get_or_create( - first_name="Testy2", - last_name="Tester2", - title="Another Tester", - email="testy2@town.com", - phone="(555) 555 5557", - ) - application, _ = DomainApplication.objects.get_or_create( - organization_type="federal", - federal_type="executive", - purpose="Purpose of the site", - anything_else="No", - is_policy_acknowledged=True, - organization_name="Testorg", - address_line1="address 1", - state_territory="NY", - zipcode="10002", - authorizing_official=ao, - requested_domain=domain, - submitter=you, - creator=self.user, - ) - application.other_contacts.add(other) - application.current_websites.add(current) - application.alternative_domains.add(alt) - - # prime the form by visiting /edit - url = reverse("edit-application", kwargs={"id": application.pk}) - response = self.client.get(url) - - # TODO: this is a sketch of each page in the wizard which needs to be tested - # Django does not have tools sufficient for real end to end integration testing - # (for example, USWDS moves radio buttons off screen and replaces them with - # CSS styled "fakes" -- Django cannot determine if those are visually correct) - # -- the best that can/should be done here is to ensure the correct values - # are being passed to the templating engine - - url = reverse("application:organization_type") - response = self.client.get(url, follow=True) - self.assertContains(response, "") - # choices = response.context['wizard']['form']['organization_type'].subwidgets - # radio = [ x for x in choices if x.data["value"] == "federal" ][0] - # checked = radio.data["selected"] - # self.assertTrue(checked) - - # url = reverse("application:organization_federal") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:organization_contact") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:authorizing_official") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:current_sites") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:dotgov_domain") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:purpose") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:your_contact") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:other_contacts") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:other_contacts") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:security_email") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:anything_else") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - # url = reverse("application:requirements") - # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # page = self.app.get(url) - # self.assertNotContains(page, "VALUE") - - def test_long_org_name_in_application(self): - """ - Make sure the long name is displaying in the application form, - org step - """ - intro_page = self.app.get(reverse("application:")) - # django-webtest does not handle cookie-based sessions well because it keeps - # resetting the session key on each new request, thus destroying the concept - # of a "session". We are going to do it manually, saving the session ID here - # and then setting the cookie on each request. - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - intro_form = intro_page.forms[0] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - intro_result = intro_form.submit() - - # follow first redirect - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_page = intro_result.follow() - - self.assertContains(type_page, "Federal: an agency of the U.S. government") - - def test_submit_modal_no_domain_text_fallback(self): - """When user clicks on submit your domain request and the requested domain - is null (possible through url direct access to the review page), present - fallback copy in the modal's header. - - NOTE: This may be a moot point if we implement a more solid pattern in the - future, like not a submit action at all on the review page.""" - - review_page = self.app.get(reverse("application:review")) - self.assertContains(review_page, "toggle-submit-domain-request") - self.assertContains(review_page, "You are about to submit an incomplete request") - - -class TestWithDomainPermissions(TestWithUser): - def setUp(self): - super().setUp() - self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") - self.domain_with_ip, _ = Domain.objects.get_or_create(name="nameserverwithip.gov") - self.domain_just_nameserver, _ = Domain.objects.get_or_create(name="justnameserver.com") - self.domain_no_information, _ = Domain.objects.get_or_create(name="noinformation.gov") - self.domain_on_hold, _ = Domain.objects.get_or_create( - name="on-hold.gov", - state=Domain.State.ON_HOLD, - expiration_date=timezone.make_aware( - datetime.combine(date.today() + timedelta(days=1), datetime.min.time()) - ), - ) - self.domain_deleted, _ = Domain.objects.get_or_create( - name="deleted.gov", - state=Domain.State.DELETED, - expiration_date=timezone.make_aware( - datetime.combine(date.today() + timedelta(days=1), datetime.min.time()) - ), - ) - - self.domain_dsdata, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") - self.domain_multdsdata, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov") - # We could simply use domain (igorville) but this will be more readable in tests - # that inherit this setUp - self.domain_dnssec_none, _ = Domain.objects.get_or_create(name="dnssec-none.gov") - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dsdata) - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_multdsdata) - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dnssec_none) - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_ip) - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_just_nameserver) - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_on_hold) - DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_deleted) - - self.role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER - ) - - UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.MANAGER - ) - UserDomainRole.objects.get_or_create( - user=self.user, - domain=self.domain_multdsdata, - role=UserDomainRole.Roles.MANAGER, - ) - UserDomainRole.objects.get_or_create( - user=self.user, - domain=self.domain_dnssec_none, - role=UserDomainRole.Roles.MANAGER, - ) - UserDomainRole.objects.get_or_create( - user=self.user, - domain=self.domain_with_ip, - role=UserDomainRole.Roles.MANAGER, - ) - UserDomainRole.objects.get_or_create( - user=self.user, - domain=self.domain_just_nameserver, - role=UserDomainRole.Roles.MANAGER, - ) - UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain_on_hold, role=UserDomainRole.Roles.MANAGER - ) - UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain_deleted, role=UserDomainRole.Roles.MANAGER - ) - - def tearDown(self): - try: - UserDomainRole.objects.all().delete() - if hasattr(self.domain, "contacts"): - self.domain.contacts.all().delete() - DomainApplication.objects.all().delete() - DomainInformation.objects.all().delete() - PublicContact.objects.all().delete() - HostIP.objects.all().delete() - Host.objects.all().delete() - Domain.objects.all().delete() - UserDomainRole.objects.all().delete() - except ValueError: # pass if already deleted - pass - super().tearDown() - - -class TestDomainPermissions(TestWithDomainPermissions): - def test_not_logged_in(self): - """Not logged in gets a redirect to Login.""" - for view_name in [ - "domain", - "domain-users", - "domain-users-add", - "domain-dns-nameservers", - "domain-org-name-address", - "domain-authorizing-official", - "domain-your-contact-information", - "domain-security-email", - ]: - with self.subTest(view_name=view_name): - response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) - self.assertEqual(response.status_code, 302) - - def test_no_domain_role(self): - """Logged in but no role gets 403 Forbidden.""" - self.client.force_login(self.user) - self.role.delete() # user no longer has a role on this domain - - for view_name in [ - "domain", - "domain-users", - "domain-users-add", - "domain-dns-nameservers", - "domain-org-name-address", - "domain-authorizing-official", - "domain-your-contact-information", - "domain-security-email", - ]: - with self.subTest(view_name=view_name): - with less_console_noise(): - response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) - self.assertEqual(response.status_code, 403) - - def test_domain_pages_blocked_for_on_hold_and_deleted(self): - """Test that the domain pages are blocked for on hold and deleted domains""" - - self.client.force_login(self.user) - for view_name in [ - "domain-users", - "domain-users-add", - "domain-dns", - "domain-dns-nameservers", - "domain-dns-dnssec", - "domain-dns-dnssec-dsdata", - "domain-org-name-address", - "domain-authorizing-official", - "domain-your-contact-information", - "domain-security-email", - ]: - for domain in [ - self.domain_on_hold, - self.domain_deleted, - ]: - with self.subTest(view_name=view_name, domain=domain): - with less_console_noise(): - response = self.client.get(reverse(view_name, kwargs={"pk": domain.id})) - self.assertEqual(response.status_code, 403) - - -class TestDomainOverview(TestWithDomainPermissions, WebTest): - def setUp(self): - super().setUp() - self.app.set_user(self.user.username) - self.client.force_login(self.user) - - -class TestDomainDetail(TestDomainOverview): - @skip("Assertion broke for no reason, why? Need to fix") - def test_domain_detail_link_works(self): - home_page = self.app.get("/") - logger.info(f"This is the value of home_page: {home_page}") - self.assertContains(home_page, "igorville.gov") - # click the "Edit" link - detail_page = home_page.click("Manage", index=0) - self.assertContains(detail_page, "igorville.gov") - self.assertContains(detail_page, "Status") - - def test_unknown_domain_does_not_show_as_expired_on_homepage(self): - """An UNKNOWN domain does not show as expired on the homepage. - It shows as 'DNS needed'""" - # At the time of this test's writing, there are 6 UNKNOWN domains inherited - # from constructors. Let's reset. - Domain.objects.all().delete() - UserDomainRole.objects.all().delete() - self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") - home_page = self.app.get("/") - self.assertNotContains(home_page, "igorville.gov") - self.role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER - ) - home_page = self.app.get("/") - self.assertContains(home_page, "igorville.gov") - igorville = Domain.objects.get(name="igorville.gov") - self.assertEquals(igorville.state, Domain.State.UNKNOWN) - self.assertNotContains(home_page, "Expired") - self.assertContains(home_page, "DNS needed") - - def test_unknown_domain_does_not_show_as_expired_on_detail_page(self): - """An UNKNOWN domain does not show as expired on the detail page. - It shows as 'DNS needed'""" - # At the time of this test's writing, there are 6 UNKNOWN domains inherited - # from constructors. Let's reset. - Domain.objects.all().delete() - UserDomainRole.objects.all().delete() - - self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - self.role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER - ) - - home_page = self.app.get("/") - self.assertContains(home_page, "igorville.gov") - igorville = Domain.objects.get(name="igorville.gov") - self.assertEquals(igorville.state, Domain.State.UNKNOWN) - detail_page = home_page.click("Manage", index=0) - self.assertNotContains(detail_page, "Expired") - - self.assertContains(detail_page, "DNS needed") - - def test_domain_detail_blocked_for_ineligible_user(self): - """We could easily duplicate this test for all domain management - views, but a single url test should be solid enough since all domain - management pages share the same permissions class""" - self.user.status = User.RESTRICTED - self.user.save() - home_page = self.app.get("/") - self.assertContains(home_page, "igorville.gov") - with less_console_noise(): - response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) - self.assertEqual(response.status_code, 403) - - def test_domain_detail_allowed_for_on_hold(self): - """Test that the domain overview page displays for on hold domain""" - home_page = self.app.get("/") - self.assertContains(home_page, "on-hold.gov") - - # View domain overview page - detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_on_hold.id})) - self.assertNotContains(detail_page, "Edit") - - def test_domain_detail_see_just_nameserver(self): - home_page = self.app.get("/") - self.assertContains(home_page, "justnameserver.com") - - # View nameserver on Domain Overview page - detail_page = self.app.get(reverse("domain", kwargs={"pk": self.domain_just_nameserver.id})) - - self.assertContains(detail_page, "justnameserver.com") - self.assertContains(detail_page, "ns1.justnameserver.com") - self.assertContains(detail_page, "ns2.justnameserver.com") - - def test_domain_detail_see_nameserver_and_ip(self): - home_page = self.app.get("/") - self.assertContains(home_page, "nameserverwithip.gov") - - # View nameserver on Domain Overview page - detail_page = self.app.get(reverse("domain", kwargs={"pk": self.domain_with_ip.id})) - - self.assertContains(detail_page, "nameserverwithip.gov") - - self.assertContains(detail_page, "ns1.nameserverwithip.gov") - self.assertContains(detail_page, "ns2.nameserverwithip.gov") - self.assertContains(detail_page, "ns3.nameserverwithip.gov") - # Splitting IP addresses bc there is odd whitespace and can't strip text - self.assertContains(detail_page, "(1.2.3.4,") - self.assertContains(detail_page, "2.3.4.5)") - - def test_domain_detail_with_no_information_or_application(self): - """Test that domain management page returns 200 and displays error - when no domain information or domain application exist""" - # have to use staff user for this test - staff_user = create_user() - # staff_user.save() - self.client.force_login(staff_user) - - # need to set the analyst_action and analyst_action_location - # in the session to emulate user clicking Manage Domain - # in the admin interface - session = self.client.session - session["analyst_action"] = "foo" - session["analyst_action_location"] = self.domain_no_information.id - session.save() - - detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_no_information.id})) - - self.assertContains(detail_page, "noinformation.gov") - self.assertContains(detail_page, "Domain missing domain information") - - -class TestDomainManagers(TestDomainOverview): - def tearDown(self): - """Ensure that the user has its original permissions""" - super().tearDown() - self.user.is_staff = False - self.user.save() - - def test_domain_managers(self): - response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) - self.assertContains(response, "Domain managers") - - def test_domain_managers_add_link(self): - """Button to get to user add page works.""" - management_page = self.app.get(reverse("domain-users", kwargs={"pk": self.domain.id})) - add_page = management_page.click("Add a domain manager") - self.assertContains(add_page, "Add a domain manager") - - def test_domain_user_add(self): - response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - self.assertContains(response, "Add a domain manager") - - @boto3_mocking.patching - def test_domain_user_add_form(self): - """Adding an existing user works.""" - other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov") - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - add_page.form["email"] = "mayor@igorville.gov" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - success_result = add_page.form.submit() - - self.assertEqual(success_result.status_code, 302) - self.assertEqual( - success_result["Location"], - reverse("domain-users", kwargs={"pk": self.domain.id}), - ) - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - success_page = success_result.follow() - self.assertContains(success_page, "mayor@igorville.gov") - - @boto3_mocking.patching - def test_domain_invitation_created(self): - """Add user on a nonexistent email creates an invitation. - - Adding a non-existent user sends an email as a side-effect, so mock - out the boto3 SES email sending here. - """ - # make sure there is no user with this email - email_address = "mayor@igorville.gov" - User.objects.filter(email=email_address).delete() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - success_result = add_page.form.submit() - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - success_page = success_result.follow() - - self.assertContains(success_page, email_address) - self.assertContains(success_page, "Cancel") # link to cancel invitation - self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) - - @boto3_mocking.patching - def test_domain_invitation_created_for_caps_email(self): - """Add user on a nonexistent email with CAPS creates an invitation to lowercase email. - - Adding a non-existent user sends an email as a side-effect, so mock - out the boto3 SES email sending here. - """ - # make sure there is no user with this email - email_address = "mayor@igorville.gov" - caps_email_address = "MAYOR@igorville.gov" - User.objects.filter(email=email_address).delete() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = caps_email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - success_result = add_page.form.submit() - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - success_page = success_result.follow() - - self.assertContains(success_page, email_address) - self.assertContains(success_page, "Cancel") # link to cancel invitation - self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) - - @boto3_mocking.patching - def test_domain_invitation_email_sent(self): - """Inviting a non-existent user sends them an email.""" - # make sure there is no user with this email - email_address = "mayor@igorville.gov" - User.objects.filter(email=email_address).delete() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - mock_client = MagicMock() - mock_client_instance = mock_client.return_value - 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})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() - - # check the mock instance to see if `send_email` was called right - mock_client_instance.send_email.assert_called_once_with( - FromEmailAddress=settings.DEFAULT_FROM_EMAIL, - Destination={"ToAddresses": [email_address]}, - Content=ANY, - ) - - @boto3_mocking.patching - 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.""" - # make sure there is no user with this email - email_address = "mayor@igorville.gov" - User.objects.filter(email=email_address).delete() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - mock_client = MagicMock() - mock_client_instance = mock_client.return_value - - 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})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() - - # check the mock instance to see if `send_email` was called right - mock_client_instance.send_email.assert_called_once_with( - FromEmailAddress=settings.DEFAULT_FROM_EMAIL, - Destination={"ToAddresses": [email_address]}, - Content=ANY, - ) - - # Check the arguments passed to send_email method - _, kwargs = mock_client_instance.send_email.call_args - - # Extract the email content, and check that the message is as we expect - email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] - self.assertIn("info@example.com", email_content) - - # Check that the requestors first/last name do not exist - self.assertNotIn("First", email_content) - self.assertNotIn("Last", email_content) - self.assertNotIn("First Last", email_content) - - @boto3_mocking.patching - def test_domain_invitation_email_has_email_as_requestor(self): - """Inviting a user sends them an email, with email as the name.""" - # Create a fake user object - email_address = "mayor@igorville.gov" - User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com") - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - mock_client = MagicMock() - mock_client_instance = mock_client.return_value - - 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})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() - - # check the mock instance to see if `send_email` was called right - mock_client_instance.send_email.assert_called_once_with( - FromEmailAddress=settings.DEFAULT_FROM_EMAIL, - Destination={"ToAddresses": [email_address]}, - Content=ANY, - ) - - # Check the arguments passed to send_email method - _, kwargs = mock_client_instance.send_email.call_args - - # Extract the email content, and check that the message is as we expect - email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] - self.assertIn("info@example.com", email_content) - - # Check that the requestors first/last name do not exist - self.assertNotIn("First", email_content) - self.assertNotIn("Last", email_content) - self.assertNotIn("First Last", email_content) - - @boto3_mocking.patching - def test_domain_invitation_email_has_email_as_requestor_staff(self): - """Inviting a user sends them an email, with email as the name.""" - # Create a fake user object - email_address = "mayor@igorville.gov" - User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com") - - # Make sure the user is staff - self.user.is_staff = True - self.user.save() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - mock_client = MagicMock() - mock_client_instance = mock_client.return_value - - 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})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit() - - # check the mock instance to see if `send_email` was called right - mock_client_instance.send_email.assert_called_once_with( - FromEmailAddress=settings.DEFAULT_FROM_EMAIL, - Destination={"ToAddresses": [email_address]}, - Content=ANY, - ) - - # Check the arguments passed to send_email method - _, kwargs = mock_client_instance.send_email.call_args - - # Extract the email content, and check that the message is as we expect - email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] - self.assertIn("help@get.gov", email_content) - - # Check that the requestors first/last name do not exist - self.assertNotIn("First", email_content) - self.assertNotIn("Last", email_content) - self.assertNotIn("First Last", email_content) - - @boto3_mocking.patching - def test_domain_invitation_email_displays_error_non_existent(self): - """Inviting a non existent user sends them an email, with email as the name.""" - # make sure there is no user with this email - email_address = "mayor@igorville.gov" - User.objects.filter(email=email_address).delete() - - # Give the user who is sending the email an invalid email address - self.user.email = "" - self.user.save() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - mock_client = MagicMock() - mock_error_message = MagicMock() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - 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})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit().follow() - - expected_message_content = "Can't send invitation email. No email is associated with your account." - - # Grab the message content - returned_error_message = mock_error_message.call_args[0][1] - - # Check that the message content is what we expect - self.assertEqual(expected_message_content, returned_error_message) - - @boto3_mocking.patching - def test_domain_invitation_email_displays_error(self): - """When the requesting user has no email, an error is displayed""" - # make sure there is no user with this email - # Create a fake user object - email_address = "mayor@igorville.gov" - User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com") - - # Give the user who is sending the email an invalid email address - self.user.email = "" - self.user.save() - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - mock_client = MagicMock() - - mock_error_message = MagicMock() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - 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})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - add_page.form.submit().follow() - - expected_message_content = "Can't send invitation email. No email is associated with your account." - - # Grab the message content - returned_error_message = mock_error_message.call_args[0][1] - - # Check that the message content is what we expect - self.assertEqual(expected_message_content, returned_error_message) - - def test_domain_invitation_cancel(self): - """Posting to the delete view deletes an invitation.""" - email_address = "mayor@igorville.gov" - invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) - mock_client.EMAILS_SENT.clear() - with self.assertRaises(DomainInvitation.DoesNotExist): - DomainInvitation.objects.get(id=invitation.id) - - def test_domain_invitation_cancel_no_permissions(self): - """Posting to the delete view as a different user should fail.""" - email_address = "mayor@igorville.gov" - invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) - - other_user = User() - other_user.save() - self.client.force_login(other_user) - mock_client = MagicMock() - 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})) - - self.assertEqual(result.status_code, 403) - - @boto3_mocking.patching - def test_domain_invitation_flow(self): - """Send an invitation to a new user, log in and load the dashboard.""" - email_address = "mayor@igorville.gov" - User.objects.filter(email=email_address).delete() - - add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) - - self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) - - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = email_address - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - - mock_client = MagicMock() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - add_page.form.submit() - - # user was invited, create them - new_user = User.objects.create(username=email_address, email=email_address) - # log them in to `self.app` - self.app.set_user(new_user.username) - # and manually call the on each login callback - new_user.on_each_login() - - # Now load the home page and make sure our domain appears there - home_page = self.app.get(reverse("home")) - self.assertContains(home_page, self.domain.name) - - -class TestDomainNameservers(TestDomainOverview): - def test_domain_nameservers(self): - """Can load domain's nameservers page.""" - page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - self.assertContains(page, "DNS name servers") - - def test_domain_nameservers_form_submit_one_nameserver(self): - """Nameserver form submitted with one nameserver throws error. - - Uses self.app WebTest because we need to interact with forms. - """ - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form with only one nameserver, should error - # regarding required fields - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. form requires a minimum of 2 name servers - self.assertContains( - result, - "At least two name servers are required.", - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submit_subdomain_missing_ip(self): - """Nameserver form catches missing ip error on subdomain. - - Uses self.app WebTest because we need to interact with forms. - """ - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without two hosts, both subdomains, - # only one has ips - nameservers_page.form["form-1-server"] = "ns2.igorville.gov" - - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. subdomain missing an ip - self.assertContains( - result, - str(NameserverError(code=NameserverErrorCodes.MISSING_IP)), - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submit_missing_host(self): - """Nameserver form catches error when host is missing. - - Uses self.app WebTest because we need to interact with forms. - """ - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without two hosts, both subdomains, - # only one has ips - nameservers_page.form["form-1-ip"] = "127.0.0.1" - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. nameserver has ip but missing host - self.assertContains( - result, - str(NameserverError(code=NameserverErrorCodes.MISSING_HOST)), - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submit_duplicate_host(self): - """Nameserver form catches error when host is duplicated. - - Uses self.app WebTest because we need to interact with forms. - """ - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form with duplicate host names of fake.host.com - nameservers_page.form["form-0-ip"] = "" - nameservers_page.form["form-1-server"] = "fake.host.com" - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. remove duplicate entry - self.assertContains( - result, - str(NameserverError(code=NameserverErrorCodes.DUPLICATE_HOST)), - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submit_whitespace(self): - """Nameserver form removes whitespace from ip. - - Uses self.app WebTest because we need to interact with forms. - """ - nameserver1 = "ns1.igorville.gov" - nameserver2 = "ns2.igorville.gov" - valid_ip = "1.1. 1.1" - # initial nameservers page has one server with two ips - # have to throw an error in order to test that the whitespace has been stripped from ip - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without one host and an ip with whitespace - nameservers_page.form["form-0-server"] = nameserver1 - nameservers_page.form["form-1-ip"] = valid_ip - nameservers_page.form["form-1-server"] = nameserver2 - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # form submission was a post with an ip address which has been stripped of whitespace, - # response should be a 302 to success page - self.assertEqual(result.status_code, 302) - self.assertEqual( - result["Location"], - reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), - ) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - page = result.follow() - # in the event of a generic nameserver error from registry error, there will be a 302 - # 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") - - def test_domain_nameservers_form_submit_glue_record_not_allowed(self): - """Nameserver form catches error when IP is present - but host not subdomain. - - Uses self.app WebTest because we need to interact with forms. - """ - nameserver1 = "ns1.igorville.gov" - nameserver2 = "ns2.igorville.com" - valid_ip = "127.0.0.1" - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without two hosts, both subdomains, - # only one has ips - nameservers_page.form["form-0-server"] = nameserver1 - nameservers_page.form["form-1-server"] = nameserver2 - nameservers_page.form["form-1-ip"] = valid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. nameserver has ip but missing host - self.assertContains( - result, - str(NameserverError(code=NameserverErrorCodes.GLUE_RECORD_NOT_ALLOWED)), - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submit_invalid_ip(self): - """Nameserver form catches invalid IP on submission. - - Uses self.app WebTest because we need to interact with forms. - """ - nameserver = "ns2.igorville.gov" - invalid_ip = "123" - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without two hosts, both subdomains, - # only one has ips - nameservers_page.form["form-1-server"] = nameserver - nameservers_page.form["form-1-ip"] = invalid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. nameserver has ip but missing host - self.assertContains( - result, - str(NameserverError(code=NameserverErrorCodes.INVALID_IP, nameserver=nameserver)), - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submit_invalid_host(self): - """Nameserver form catches invalid host on submission. - - Uses self.app WebTest because we need to interact with forms. - """ - nameserver = "invalid-nameserver.gov" - valid_ip = "123.2.45.111" - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without two hosts, both subdomains, - # only one has ips - nameservers_page.form["form-1-server"] = nameserver - nameservers_page.form["form-1-ip"] = valid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # 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 - # the required field. nameserver has invalid host - self.assertContains( - result, - str(NameserverError(code=NameserverErrorCodes.INVALID_HOST, nameserver=nameserver)), - count=2, - status_code=200, - ) - - def test_domain_nameservers_form_submits_successfully(self): - """Nameserver form submits successfully with valid input. - - Uses self.app WebTest because we need to interact with forms. - """ - nameserver1 = "ns1.igorville.gov" - nameserver2 = "ns2.igorville.gov" - valid_ip = "127.0.0.1" - # initial nameservers page has one server with two ips - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # attempt to submit the form without two hosts, both subdomains, - # only one has ips - nameservers_page.form["form-0-server"] = nameserver1 - nameservers_page.form["form-1-server"] = nameserver2 - nameservers_page.form["form-1-ip"] = valid_ip - with less_console_noise(): # swallow log warning message - result = nameservers_page.form.submit() - # form submission was a successful post, response should be a 302 - self.assertEqual(result.status_code, 302) - self.assertEqual( - result["Location"], - reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), - ) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - page = result.follow() - self.assertContains(page, "The name servers for this domain have been updated") - - def test_domain_nameservers_form_invalid(self): - """Nameserver form does not submit with invalid data. - - Uses self.app WebTest because we need to interact with forms. - """ - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # first two nameservers are required, so if we empty one out we should - # get a form error - nameservers_page.form["form-0-server"] = "" - with less_console_noise(): # swallow logged warning message - result = nameservers_page.form.submit() - # 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, - # once around each required field. - self.assertContains( - result, - "At least two name servers are required.", - count=4, - status_code=200, - ) - - -class TestDomainAuthorizingOfficial(TestDomainOverview): - def test_domain_authorizing_official(self): - """Can load domain's authorizing official page.""" - page = self.client.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) - # once on the sidebar, once in the title - self.assertContains(page, "Authorizing official", count=2) - - def test_domain_authorizing_official_content(self): - """Authorizing official information appears on the page.""" - self.domain_information.authorizing_official = Contact(first_name="Testy") - self.domain_information.authorizing_official.save() - self.domain_information.save() - page = self.app.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) - self.assertContains(page, "Testy") - - def test_domain_edit_authorizing_official_in_place(self): - """When editing an authorizing official for domain information and AO is not - joined to any other objects""" - self.domain_information.authorizing_official = Contact( - first_name="Testy", last_name="Tester", title="CIO", email="nobody@igorville.gov" - ) - self.domain_information.authorizing_official.save() - self.domain_information.save() - ao_page = self.app.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_form = ao_page.forms[0] - self.assertEqual(ao_form["first_name"].value, "Testy") - ao_form["first_name"] = "Testy2" - # ao_pk is the initial pk of the authorizing official. set it before update - # to be able to verify after update that the same contact object is in place - ao_pk = self.domain_information.authorizing_official.id - ao_form.submit() - - # refresh domain information - self.domain_information.refresh_from_db() - self.assertEqual("Testy2", self.domain_information.authorizing_official.first_name) - self.assertEqual(ao_pk, self.domain_information.authorizing_official.id) - - def test_domain_edit_authorizing_official_creates_new(self): - """When editing an authorizing official for domain information and AO IS - joined to another object""" - # set AO and Other Contact to the same Contact object - self.domain_information.authorizing_official = Contact( - first_name="Testy", last_name="Tester", title="CIO", email="nobody@igorville.gov" - ) - self.domain_information.authorizing_official.save() - self.domain_information.save() - self.domain_information.other_contacts.add(self.domain_information.authorizing_official) - self.domain_information.save() - # load the Authorizing Official in the web form - ao_page = self.app.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - ao_form = ao_page.forms[0] - # verify the first name is "Testy" and then change it to "Testy2" - self.assertEqual(ao_form["first_name"].value, "Testy") - ao_form["first_name"] = "Testy2" - # ao_pk is the initial pk of the authorizing official. set it before update - # to be able to verify after update that the same contact object is in place - ao_pk = self.domain_information.authorizing_official.id - ao_form.submit() - - # refresh domain information - self.domain_information.refresh_from_db() - # assert that AO information is updated, and that the AO is a new Contact - self.assertEqual("Testy2", self.domain_information.authorizing_official.first_name) - self.assertNotEqual(ao_pk, self.domain_information.authorizing_official.id) - # assert that the Other Contact information is not updated and that the Other Contact - # is the original Contact object - other_contact = self.domain_information.other_contacts.all()[0] - self.assertEqual("Testy", other_contact.first_name) - self.assertEqual(ao_pk, other_contact.id) - - -class TestDomainOrganization(TestDomainOverview): - def test_domain_org_name_address(self): - """Can load domain's org name and mailing address page.""" - 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 - self.assertContains(page, "Organization name and mailing address", count=3) - - def test_domain_org_name_address_content(self): - """Org name and address information appears on the page.""" - self.domain_information.organization_name = "Town of Igorville" - self.domain_information.save() - page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) - self.assertContains(page, "Town of Igorville") - - def test_domain_org_name_address_form(self): - """Submitting changes works on the org name address page.""" - self.domain_information.organization_name = "Town of Igorville" - self.domain_information.save() - org_name_page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - - org_name_page.form["organization_name"] = "Not igorville" - org_name_page.form["city"] = "Faketown" - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - success_result_page = org_name_page.form.submit() - self.assertEqual(success_result_page.status_code, 200) - - self.assertContains(success_result_page, "Not igorville") - self.assertContains(success_result_page, "Faketown") - - -class TestDomainContactInformation(TestDomainOverview): - def test_domain_your_contact_information(self): - """Can load domain's your contact information page.""" - page = self.client.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})) - self.assertContains(page, "Your contact information") - - def test_domain_your_contact_information_content(self): - """Logged-in user's contact information appears on the page.""" - self.user.contact.first_name = "Testy" - self.user.contact.save() - page = self.app.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})) - self.assertContains(page, "Testy") - - -class TestDomainSecurityEmail(TestDomainOverview): - def test_domain_security_email_existing_security_contact(self): - """Can load domain's security email page.""" - self.mockSendPatch = patch("registrar.models.domain.registry.send") - self.mockedSendFunction = self.mockSendPatch.start() - self.mockedSendFunction.side_effect = self.mockSend - - domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov") - # Add current user to this domain - _ = UserDomainRole(user=self.user, domain=domain_contact, role="admin").save() - page = self.client.get(reverse("domain-security-email", kwargs={"pk": domain_contact.id})) - - # Loads correctly - self.assertContains(page, "Security email") - self.assertContains(page, "security@mail.gov") - self.mockSendPatch.stop() - - def test_domain_security_email_no_security_contact(self): - """Loads a domain with no defined security email. - We should not show the default.""" - self.mockSendPatch = patch("registrar.models.domain.registry.send") - self.mockedSendFunction = self.mockSendPatch.start() - self.mockedSendFunction.side_effect = self.mockSend - - page = self.client.get(reverse("domain-security-email", kwargs={"pk": self.domain.id})) - - # Loads correctly - self.assertContains(page, "Security email") - self.assertNotContains(page, "dotgov@cisa.dhs.gov") - self.mockSendPatch.stop() - - def test_domain_security_email(self): - """Can load domain's security email page.""" - page = self.client.get(reverse("domain-security-email", kwargs={"pk": self.domain.id})) - self.assertContains(page, "Security email") - - def test_domain_security_email_form(self): - """Adding a security email works. - Uses self.app WebTest because we need to interact with forms. - """ - security_email_page = self.app.get(reverse("domain-security-email", kwargs={"pk": self.domain.id})) - session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - security_email_page.form["security_email"] = "mayor@igorville.gov" - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - mock_client = MagicMock() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): # swallow log warning message - result = security_email_page.form.submit() - self.assertEqual(result.status_code, 302) - self.assertEqual( - result["Location"], - reverse("domain-security-email", kwargs={"pk": self.domain.id}), - ) - - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - success_page = result.follow() - self.assertContains(success_page, "The security email for this domain has been updated") - - def test_security_email_form_messages(self): - """ - Test against the success and error messages that are defined in the view - """ - p = "adminpass" - self.client.login(username="superuser", password=p) - - form_data_registry_error = { - "security_email": "test@failCreate.gov", - } - - form_data_contact_error = { - "security_email": "test@contactError.gov", - } - - form_data_success = { - "security_email": "test@something.gov", - } - - test_cases = [ - ( - "RegistryError", - form_data_registry_error, - str(GenericError(code=GenericErrorCodes.CANNOT_CONTACT_REGISTRY)), - ), - ( - "ContactError", - form_data_contact_error, - str(SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA)), - ), - ( - "RegistrySuccess", - form_data_success, - "The security email for this domain has been updated.", - ), - # Add more test cases with different scenarios here - ] - - for test_name, data, expected_message in test_cases: - response = self.client.post( - reverse("domain-security-email", kwargs={"pk": self.domain.id}), - data=data, - follow=True, - ) - - # Check the response status code, content, or any other relevant assertions - self.assertEqual(response.status_code, 200) - - # Check if the expected message tag is set - if test_name == "RegistryError" or test_name == "ContactError": - message_tag = "error" - elif test_name == "RegistrySuccess": - message_tag = "success" - else: - # Handle other cases if needed - message_tag = "info" # Change to the appropriate default - - # Check the message tag - messages = list(response.context["messages"]) - self.assertEqual(len(messages), 1) - message = messages[0] - self.assertEqual(message.tags, message_tag) - self.assertEqual(message.message.strip(), expected_message.strip()) - - def test_domain_overview_blocked_for_ineligible_user(self): - """We could easily duplicate this test for all domain management - views, but a single url test should be solid enough since all domain - management pages share the same permissions class""" - self.user.status = User.RESTRICTED - self.user.save() - home_page = self.app.get("/") - self.assertContains(home_page, "igorville.gov") - with less_console_noise(): - response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) - self.assertEqual(response.status_code, 403) - - -class TestDomainDNSSEC(TestDomainOverview): - - """MockEPPLib is already inherited.""" - - def test_dnssec_page_refreshes_enable_button(self): - """DNSSEC overview page loads when domain has no DNSSEC data - and shows a 'Enable DNSSEC' button.""" - - page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})) - self.assertContains(page, "Enable DNSSEC") - - def test_dnssec_page_loads_with_data_in_domain(self): - """DNSSEC overview page loads when domain has DNSSEC data - and the template contains a button to disable DNSSEC.""" - - page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain_multdsdata.id})) - self.assertContains(page, "Disable DNSSEC") - - # Prepare the data for the POST request - post_data = { - "disable_dnssec": "Disable DNSSEC", - } - updated_page = self.client.post( - reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}), - post_data, - follow=True, - ) - - self.assertEqual(updated_page.status_code, 200) - - self.assertContains(updated_page, "Enable DNSSEC") - - def test_ds_form_loads_with_no_domain_data(self): - """DNSSEC Add DS data page loads when there is no - domain DNSSEC data and shows a button to Add new record""" - - page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id})) - self.assertContains(page, "You have no DS data added") - self.assertContains(page, "Add new record") - - def test_ds_form_loads_with_ds_data(self): - """DNSSEC Add DS data page loads when there is - domain DNSSEC DS data and shows the data""" - - page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) - self.assertContains(page, "DS data record 1") - - def test_ds_data_form_modal(self): - """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})) - # Assert that a hidden trigger for the modal does not exist. - # This hidden trigger will pop on the page when certain condition are met: - # 1) Initial form contained DS data, 2) All data is deleted and form is - # submitted. - self.assertNotContains(add_data_page, "Trigger Disable DNSSEC Modal") - # Simulate a delete all data - form_data = {} - response = self.client.post( - reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}), - data=form_data, - ) - self.assertEqual(response.status_code, 200) # Adjust status code as needed - # Now check to see whether the JS trigger for the modal is present on the page - self.assertContains(response, "Trigger Disable DNSSEC Modal") - - def test_ds_data_form_submits(self): - """DS data form submits successfully - - Uses self.app WebTest because we need to interact with forms. - """ - 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] - 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() - # form submission was a post, response should be a redirect - self.assertEqual(result.status_code, 302) - self.assertEqual( - result["Location"], - reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}), - ) - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - page = result.follow() - self.assertContains(page, "The DS data records for this domain have been updated.") - - def test_ds_data_form_invalid(self): - """DS data form errors with invalid data (missing required fields) - - Uses self.app WebTest because we need to interact with forms. - """ - 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] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # all four form fields are required, so will test with each blank - add_data_page.forms[0]["form-0-key_tag"] = "" - 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"] = "" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() - # 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 - # the field. - self.assertContains(result, "Key tag is required", count=2, status_code=200) - self.assertContains(result, "Algorithm 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) - - def test_ds_data_form_invalid_keytag(self): - """DS data form errors with invalid data (key tag too large) - - Uses self.app WebTest because we need to interact with forms. - """ - 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] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # first two nameservers are required, so if we empty one out we should - # get a form error - add_data_page.forms[0]["form-0-key_tag"] = "65536" # > 65535 - 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"] = "" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() - # 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 - # the field. - self.assertContains( - result, str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE)), count=2, status_code=200 - ) - - def test_ds_data_form_invalid_digest_chars(self): - """DS data form errors with invalid data (digest contains non hexadecimal chars) - - Uses self.app WebTest because we need to interact with forms. - """ - 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] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # first two nameservers are required, so if we empty one out we should - # get a form error - add_data_page.forms[0]["form-0-key_tag"] = "1234" - 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"] = "GG1234" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() - # 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 - # the field. - self.assertContains( - result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)), count=2, status_code=200 - ) - - def test_ds_data_form_invalid_digest_sha1(self): - """DS data form errors with invalid data (digest is invalid sha-1) - - Uses self.app WebTest because we need to interact with forms. - """ - 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] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # first two nameservers are required, so if we empty one out we should - # get a form error - add_data_page.forms[0]["form-0-key_tag"] = "1234" - 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"] = "A123" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() - # 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 - # the field. - self.assertContains( - result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1)), count=2, status_code=200 - ) - - def test_ds_data_form_invalid_digest_sha256(self): - """DS data form errors with invalid data (digest is invalid sha-256) - - Uses self.app WebTest because we need to interact with forms. - """ - 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] - self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - # first two nameservers are required, so if we empty one out we should - # get a form error - add_data_page.forms[0]["form-0-key_tag"] = "1234" - 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"] = "GG1234" - with less_console_noise(): # swallow logged warning message - result = add_data_page.forms[0].submit() - # 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 - # the field. - self.assertContains( - result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA256)), count=2, status_code=200 - ) - - -class TestApplicationStatus(TestWithUser, WebTest): - def setUp(self): - super().setUp() - self.app.set_user(self.user.username) - self.client.force_login(self.user) - - def test_application_status(self): - """Checking application status page""" - application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) - application.save() - - home_page = self.app.get("/") - self.assertContains(home_page, "city.gov") - # click the "Manage" link - detail_page = home_page.click("Manage", index=0) - self.assertContains(detail_page, "city.gov") - self.assertContains(detail_page, "city1.gov") - self.assertContains(detail_page, "Chief Tester") - self.assertContains(detail_page, "testy@town.com") - self.assertContains(detail_page, "Admin Tester") - self.assertContains(detail_page, "Status:") - - def test_application_status_with_ineligible_user(self): - """Checking application status page whith a blocked user. - The user should still have access to view.""" - self.user.status = "ineligible" - self.user.save() - - application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) - application.save() - - home_page = self.app.get("/") - self.assertContains(home_page, "city.gov") - # click the "Manage" link - detail_page = home_page.click("Manage", index=0) - self.assertContains(detail_page, "city.gov") - self.assertContains(detail_page, "Chief Tester") - self.assertContains(detail_page, "testy@town.com") - self.assertContains(detail_page, "Admin Tester") - self.assertContains(detail_page, "Status:") - - def test_application_withdraw(self): - """Checking application status page""" - application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) - application.save() - - home_page = self.app.get("/") - self.assertContains(home_page, "city.gov") - # click the "Manage" link - detail_page = home_page.click("Manage", index=0) - self.assertContains(detail_page, "city.gov") - self.assertContains(detail_page, "city1.gov") - self.assertContains(detail_page, "Chief Tester") - self.assertContains(detail_page, "testy@town.com") - self.assertContains(detail_page, "Admin Tester") - self.assertContains(detail_page, "Status:") - # click the "Withdraw request" button - mock_client = MockSESClient() - with boto3_mocking.clients.handler_for("sesv2", mock_client): - with less_console_noise(): - withdraw_page = detail_page.click("Withdraw request") - self.assertContains(withdraw_page, "Withdraw request for") - home_page = withdraw_page.click("Withdraw request") - # confirm that it has redirected, and the status has been updated to withdrawn - self.assertRedirects( - home_page, - "/", - status_code=302, - target_status_code=200, - fetch_redirect_response=True, - ) - home_page = self.app.get("/") - self.assertContains(home_page, "Withdrawn") - - def test_application_withdraw_no_permissions(self): - """Can't withdraw applications as a restricted user.""" - self.user.status = User.RESTRICTED - self.user.save() - application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) - application.save() - - home_page = self.app.get("/") - self.assertContains(home_page, "city.gov") - # click the "Manage" link - detail_page = home_page.click("Manage", index=0) - self.assertContains(detail_page, "city.gov") - self.assertContains(detail_page, "city1.gov") - self.assertContains(detail_page, "Chief Tester") - self.assertContains(detail_page, "testy@town.com") - self.assertContains(detail_page, "Admin Tester") - self.assertContains(detail_page, "Status:") - # Restricted user trying to withdraw results in 403 error - with less_console_noise(): - for url_name in [ - "application-withdraw-confirmation", - "application-withdrawn", - ]: - with self.subTest(url_name=url_name): - page = self.client.get(reverse(url_name, kwargs={"pk": application.pk})) - self.assertEqual(page.status_code, 403) - - def test_application_status_no_permissions(self): - """Can't access applications without being the creator.""" - application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) - other_user = User() - other_user.save() - application.creator = other_user - application.save() - - # PermissionDeniedErrors make lots of noise in test output - with less_console_noise(): - for url_name in [ - "application-status", - "application-withdraw-confirmation", - "application-withdrawn", - ]: - with self.subTest(url_name=url_name): - page = self.client.get(reverse(url_name, kwargs={"pk": application.pk})) - self.assertEqual(page.status_code, 403) - - def test_approved_application_not_in_active_requests(self): - """An approved application is not shown in the Active - Requests table on home.html.""" - application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED, user=self.user) - application.save() - - home_page = self.app.get("/") - # This works in our test environment because creating - # an approved application here does not generate a - # domain object, so we do not expect to see 'city.gov' - # in either the Domains or Requests tables. - self.assertNotContains(home_page, "city.gov") diff --git a/src/registrar/tests/test_views_application.py b/src/registrar/tests/test_views_application.py new file mode 100644 index 000000000..62485fa86 --- /dev/null +++ b/src/registrar/tests/test_views_application.py @@ -0,0 +1,2199 @@ +from unittest import skip + +from django.conf import settings +from django.urls import reverse + +from .common import MockSESClient, completed_application # type: ignore +from django_webtest import WebTest # type: ignore +import boto3_mocking # type: ignore + +from registrar.models import ( + DomainApplication, + Domain, + DomainInformation, + Contact, + User, + Website, +) +from registrar.views.application import ApplicationWizard, Step + +from .common import less_console_noise +from .test_views import TestWithUser + +import logging + +logger = logging.getLogger(__name__) + + +class DomainApplicationTests(TestWithUser, WebTest): + + """Webtests for domain application to test filling and submitting.""" + + # Doesn't work with CSRF checking + # hypothesis is that CSRF_USE_SESSIONS is incompatible with WebTest + csrf_checks = False + + def setUp(self): + super().setUp() + self.app.set_user(self.user.username) + self.TITLES = ApplicationWizard.TITLES + + def test_application_form_intro_acknowledgement(self): + """Tests that user is presented with intro acknowledgement page""" + intro_page = self.app.get(reverse("application:")) + self.assertContains(intro_page, "You’re about to start your .gov domain request") + + def test_application_form_intro_is_skipped_when_edit_access(self): + """Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'""" + completed_application(status=DomainApplication.ApplicationStatus.STARTED, user=self.user) + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + # click the "Edit" link + detail_page = home_page.click("Edit", index=0) + # Check that the response is a redirect + self.assertEqual(detail_page.status_code, 302) + # You can access the 'Location' header to get the redirect URL + redirect_url = detail_page.url + self.assertEqual(redirect_url, "/request/organization_type/") + + def test_application_form_empty_submit(self): + """Tests empty submit on the first page after the acknowledgement page""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + # submitting should get back the same page if the required field is empty + result = type_page.forms[0].submit() + self.assertIn("What kind of U.S.-based government organization do you represent?", result) + + def test_application_multiple_applications_exist(self): + """Test that an info message appears when user has multiple applications already""" + # create and submit an application + application = completed_application(user=self.user) + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + application.submit() + application.save() + + # now, attempt to create another one + with less_console_noise(): + intro_page = self.app.get(reverse("application:")) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + self.assertContains(type_page, "You cannot submit this request yet") + + @boto3_mocking.patching + def test_application_form_submission(self): + """ + Can fill out the entire form and submit. + As we add additional form pages, we need to include them here to make + this test work. + + This test also looks for the long organization name on the summary page. + + This also tests for the presence of a modal trigger and the dynamic test + in the modal header on the submit page. + """ + num_pages_tested = 0 + # elections, type_of_work, tribal_government + SKIPPED_PAGES = 3 + num_pages = len(self.TITLES) - SKIPPED_PAGES + + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + # ---- TYPE PAGE ---- + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = "federal" + # test next button and validate data + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + # should see results in db + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.organization_type, "federal") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(type_result.status_code, 302) + self.assertEqual(type_result["Location"], "/request/organization_federal/") + num_pages_tested += 1 + + # ---- FEDERAL BRANCH PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + federal_page = type_result.follow() + federal_form = federal_page.forms[0] + federal_form["organization_federal-federal_type"] = "executive" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_result = federal_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.federal_type, "executive") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(federal_result.status_code, 302) + self.assertEqual(federal_result["Location"], "/request/organization_contact/") + num_pages_tested += 1 + + # ---- ORG CONTACT PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + org_contact_page = federal_result.follow() + 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"] = "General Services Administration" + org_contact_form["organization_contact-organization_name"] = "Testorg" + org_contact_form["organization_contact-address_line1"] = "address 1" + org_contact_form["organization_contact-address_line2"] = "address 2" + org_contact_form["organization_contact-city"] = "NYC" + org_contact_form["organization_contact-state_territory"] = "NY" + org_contact_form["organization_contact-zipcode"] = "10002" + org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + org_contact_result = org_contact_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.organization_name, "Testorg") + self.assertEqual(application.address_line1, "address 1") + self.assertEqual(application.address_line2, "address 2") + self.assertEqual(application.city, "NYC") + self.assertEqual(application.state_territory, "NY") + self.assertEqual(application.zipcode, "10002") + self.assertEqual(application.urbanization, "URB Royal Oaks") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(org_contact_result.status_code, 302) + self.assertEqual(org_contact_result["Location"], "/request/authorizing_official/") + num_pages_tested += 1 + + # ---- AUTHORIZING OFFICIAL PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_page = org_contact_result.follow() + ao_form = ao_page.forms[0] + ao_form["authorizing_official-first_name"] = "Testy ATO" + ao_form["authorizing_official-last_name"] = "Tester ATO" + ao_form["authorizing_official-title"] = "Chief Tester" + ao_form["authorizing_official-email"] = "testy@town.com" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_result = ao_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.authorizing_official.first_name, "Testy ATO") + self.assertEqual(application.authorizing_official.last_name, "Tester ATO") + self.assertEqual(application.authorizing_official.title, "Chief Tester") + self.assertEqual(application.authorizing_official.email, "testy@town.com") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(ao_result.status_code, 302) + self.assertEqual(ao_result["Location"], "/request/current_sites/") + num_pages_tested += 1 + + # ---- CURRENT SITES PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + current_sites_page = ao_result.follow() + current_sites_form = current_sites_page.forms[0] + current_sites_form["current_sites-0-website"] = "www.city.com" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + current_sites_result = current_sites_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual( + application.current_websites.filter(website="http://www.city.com").count(), + 1, + ) + # the post request should return a redirect to the next form in + # the application + self.assertEqual(current_sites_result.status_code, 302) + self.assertEqual(current_sites_result["Location"], "/request/dotgov_domain/") + num_pages_tested += 1 + + # ---- DOTGOV DOMAIN PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + dotgov_page = current_sites_result.follow() + dotgov_form = dotgov_page.forms[0] + dotgov_form["dotgov_domain-requested_domain"] = "city" + dotgov_form["dotgov_domain-0-alternative_domain"] = "city1" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + dotgov_result = dotgov_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.requested_domain.name, "city.gov") + self.assertEqual(application.alternative_domains.filter(website="city1.gov").count(), 1) + # the post request should return a redirect to the next form in + # the application + self.assertEqual(dotgov_result.status_code, 302) + self.assertEqual(dotgov_result["Location"], "/request/purpose/") + num_pages_tested += 1 + + # ---- PURPOSE PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + purpose_page = dotgov_result.follow() + purpose_form = purpose_page.forms[0] + purpose_form["purpose-purpose"] = "For all kinds of things." + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + purpose_result = purpose_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.purpose, "For all kinds of things.") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(purpose_result.status_code, 302) + self.assertEqual(purpose_result["Location"], "/request/your_contact/") + num_pages_tested += 1 + + # ---- YOUR CONTACT INFO PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + your_contact_page = purpose_result.follow() + your_contact_form = your_contact_page.forms[0] + + your_contact_form["your_contact-first_name"] = "Testy you" + your_contact_form["your_contact-last_name"] = "Tester you" + your_contact_form["your_contact-title"] = "Admin Tester" + your_contact_form["your_contact-email"] = "testy-admin@town.com" + your_contact_form["your_contact-phone"] = "(201) 555 5556" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + your_contact_result = your_contact_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.submitter.first_name, "Testy you") + self.assertEqual(application.submitter.last_name, "Tester you") + self.assertEqual(application.submitter.title, "Admin Tester") + self.assertEqual(application.submitter.email, "testy-admin@town.com") + self.assertEqual(application.submitter.phone, "(201) 555 5556") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(your_contact_result.status_code, 302) + self.assertEqual(your_contact_result["Location"], "/request/other_contacts/") + num_pages_tested += 1 + + # ---- OTHER CONTACTS PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + other_contacts_page = your_contact_result.follow() + + # This page has 3 forms in 1. + # Let's set the yes/no radios to enable the other contacts fieldsets + other_contacts_form = other_contacts_page.forms[0] + + other_contacts_form["other_contacts-has_other_contacts"] = "True" + + other_contacts_form["other_contacts-0-first_name"] = "Testy2" + other_contacts_form["other_contacts-0-last_name"] = "Tester2" + other_contacts_form["other_contacts-0-title"] = "Another Tester" + other_contacts_form["other_contacts-0-email"] = "testy2@town.com" + other_contacts_form["other_contacts-0-phone"] = "(201) 555 5557" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + other_contacts_result = other_contacts_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual( + application.other_contacts.filter( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(201) 555 5557", + ).count(), + 1, + ) + # the post request should return a redirect to the next form in + # the application + self.assertEqual(other_contacts_result.status_code, 302) + self.assertEqual(other_contacts_result["Location"], "/request/anything_else/") + num_pages_tested += 1 + + # ---- ANYTHING ELSE PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + anything_else_page = other_contacts_result.follow() + anything_else_form = anything_else_page.forms[0] + + anything_else_form["anything_else-anything_else"] = "Nothing else." + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + anything_else_result = anything_else_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.anything_else, "Nothing else.") + # the post request should return a redirect to the next form in + # the application + self.assertEqual(anything_else_result.status_code, 302) + self.assertEqual(anything_else_result["Location"], "/request/requirements/") + num_pages_tested += 1 + + # ---- REQUIREMENTS PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + requirements_page = anything_else_result.follow() + requirements_form = requirements_page.forms[0] + + requirements_form["requirements-is_policy_acknowledged"] = True + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + requirements_result = requirements_form.submit() + # validate that data from this step are being saved + application = DomainApplication.objects.get() # there's only one + self.assertEqual(application.is_policy_acknowledged, True) + # the post request should return a redirect to the next form in + # the application + self.assertEqual(requirements_result.status_code, 302) + self.assertEqual(requirements_result["Location"], "/request/review/") + num_pages_tested += 1 + + # ---- REVIEW AND FINSIHED PAGES ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + review_page = requirements_result.follow() + review_form = review_page.forms[0] + + # Review page contains all the previously entered data + # Let's make sure the long org name is displayed + self.assertContains(review_page, "Federal") + self.assertContains(review_page, "Executive") + self.assertContains(review_page, "Testorg") + self.assertContains(review_page, "address 1") + self.assertContains(review_page, "address 2") + self.assertContains(review_page, "NYC") + self.assertContains(review_page, "NY") + self.assertContains(review_page, "10002") + self.assertContains(review_page, "URB Royal Oaks") + self.assertContains(review_page, "Testy ATO") + self.assertContains(review_page, "Tester ATO") + self.assertContains(review_page, "Chief Tester") + self.assertContains(review_page, "testy@town.com") + self.assertContains(review_page, "city.com") + self.assertContains(review_page, "city.gov") + self.assertContains(review_page, "city1.gov") + self.assertContains(review_page, "For all kinds of things.") + self.assertContains(review_page, "Testy you") + self.assertContains(review_page, "Tester you") + self.assertContains(review_page, "Admin Tester") + self.assertContains(review_page, "testy-admin@town.com") + self.assertContains(review_page, "(201) 555-5556") + self.assertContains(review_page, "Testy2") + self.assertContains(review_page, "Tester2") + self.assertContains(review_page, "Another Tester") + self.assertContains(review_page, "testy2@town.com") + self.assertContains(review_page, "(201) 555-5557") + self.assertContains(review_page, "Nothing else.") + + # We can't test the modal itself as it relies on JS for init and triggering, + # but we can test for the existence of its trigger: + self.assertContains(review_page, "toggle-submit-domain-request") + # And the existence of the modal's data parked and ready for the js init. + # The next assert also tests for the passed requested domain context from + # the view > application_form > modal + self.assertContains(review_page, "You are about to submit a domain request for city.gov") + + # final submission results in a redirect to the "finished" URL + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + with less_console_noise(): + review_result = review_form.submit() + + self.assertEqual(review_result.status_code, 302) + self.assertEqual(review_result["Location"], "/request/finished/") + num_pages_tested += 1 + + # following this redirect is a GET request, so include the cookie + # here too. + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + with less_console_noise(): + final_result = review_result.follow() + self.assertContains(final_result, "Thanks for your domain request!") + + # check that any new pages are added to this test + self.assertEqual(num_pages, num_pages_tested) + + # This is the start of a test to check an existing application, it currently + # does not work and results in errors as noted in: + # https://github.com/cisagov/getgov/pull/728 + @skip("WIP") + def test_application_form_started_allsteps(self): + num_pages_tested = 0 + # elections, type_of_work, tribal_government + SKIPPED_PAGES = 3 + DASHBOARD_PAGE = 1 + num_pages = len(self.TITLES) - SKIPPED_PAGES + DASHBOARD_PAGE + + application = completed_application(user=self.user) + application.save() + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + self.assertContains(home_page, "Started") + num_pages_tested += 1 + + # TODO: For some reason this click results in a new application being generated + # This appraoch is an alternatie to using get as is being done below + # + # type_page = home_page.click("Edit") + + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + url = reverse("edit-application", kwargs={"id": application.pk}) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # TODO: The following line results in a django error on middleware + response = self.client.get(url, follow=True) + self.assertContains(response, "Type of organization") + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # TODO: Step through the remaining pages + + self.assertEqual(num_pages, num_pages_tested) + + def test_application_form_conditional_federal(self): + """Federal branch question is shown for federal organizations.""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + # ---- TYPE PAGE ---- + + # the conditional step titles shouldn't appear initially + self.assertNotContains(type_page, self.TITLES["organization_federal"]) + self.assertNotContains(type_page, self.TITLES["organization_election"]) + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = "federal" + + # set the session ID before .submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + + # the post request should return a redirect to the federal branch + # question + self.assertEqual(type_result.status_code, 302) + self.assertEqual(type_result["Location"], "/request/organization_federal/") + + # and the step label should appear in the sidebar of the resulting page + # but the step label for the elections page should not appear + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_page = type_result.follow() + self.assertContains(federal_page, self.TITLES["organization_federal"]) + self.assertNotContains(federal_page, self.TITLES["organization_election"]) + + # continuing on in the flow we need to see top-level agency on the + # contact page + federal_page.forms[0]["organization_federal-federal_type"] = "executive" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_result = federal_page.forms[0].submit() + # the post request should return a redirect to the contact + # question + self.assertEqual(federal_result.status_code, 302) + self.assertEqual(federal_result["Location"], "/request/organization_contact/") + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + contact_page = federal_result.follow() + self.assertContains(contact_page, "Federal agency") + + def test_application_form_conditional_elections(self): + """Election question is shown for other organizations.""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + # ---- TYPE PAGE ---- + + # the conditional step titles shouldn't appear initially + self.assertNotContains(type_page, self.TITLES["organization_federal"]) + self.assertNotContains(type_page, self.TITLES["organization_election"]) + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = "county" + + # set the session ID before .submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + + # the post request should return a redirect to the elections question + self.assertEqual(type_result.status_code, 302) + self.assertEqual(type_result["Location"], "/request/organization_election/") + + # and the step label should appear in the sidebar of the resulting page + # but the step label for the elections page should not appear + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + election_page = type_result.follow() + self.assertContains(election_page, self.TITLES["organization_election"]) + self.assertNotContains(election_page, self.TITLES["organization_federal"]) + + # continuing on in the flow we need to NOT see top-level agency on the + # contact page + election_page.forms[0]["organization_election-is_election_board"] = "True" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + election_result = election_page.forms[0].submit() + # the post request should return a redirect to the contact + # question + self.assertEqual(election_result.status_code, 302) + self.assertEqual(election_result["Location"], "/request/organization_contact/") + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + contact_page = election_result.follow() + self.assertNotContains(contact_page, "Federal agency") + + def test_application_form_section_skipping(self): + """Can skip forward and back in sections""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = "federal" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_page = type_result.follow() + + # Now on federal type page, click back to the organization type + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + new_page = federal_page.click(str(self.TITLES["organization_type"]), index=0) + + # Should be a link to the organization_federal page + self.assertGreater( + len(new_page.html.find_all("a", href="/request/organization_federal/")), + 0, + ) + + def test_application_form_nonfederal(self): + """Non-federal organizations don't have to provide their federal agency.""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + contact_page = type_result.follow() + org_contact_form = contact_page.forms[0] + + self.assertNotIn("federal_agency", org_contact_form.fields) + + # minimal fields that must be filled out + org_contact_form["organization_contact-organization_name"] = "Testorg" + org_contact_form["organization_contact-address_line1"] = "address 1" + org_contact_form["organization_contact-city"] = "NYC" + org_contact_form["organization_contact-state_territory"] = "NY" + org_contact_form["organization_contact-zipcode"] = "10002" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + contact_result = org_contact_form.submit() + + # the post request should return a redirect to the + # about your organization page if it was successful. + self.assertEqual(contact_result.status_code, 302) + self.assertEqual(contact_result["Location"], "/request/about_your_organization/") + + def test_application_about_your_organization_special(self): + """Special districts have to answer an additional question.""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_page.forms[0].submit() + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + contact_page = type_result.follow() + + self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) + + def test_yes_no_form_inits_blank_for_new_application(self): + """On the Other Contacts page, the yes/no form gets initialized with nothing selected for + new applications""" + other_contacts_page = self.app.get(reverse("application:other_contacts")) + other_contacts_form = other_contacts_page.forms[0] + self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, None) + + def test_yes_no_form_inits_yes_for_application_with_other_contacts(self): + """On the Other Contacts page, the yes/no form gets initialized with YES selected if the + application has other contacts""" + # Application has other contacts by default + application = completed_application(user=self.user) + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") + + def test_yes_no_form_inits_no_for_application_with_no_other_contacts_rationale(self): + """On the Other Contacts page, the yes/no form gets initialized with NO selected if the + application has no other contacts""" + # Application has other contacts by default + application = completed_application(user=self.user, has_other_contacts=False) + application.no_other_contacts_rationale = "Hello!" + application.save() + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False") + + def test_submitting_other_contacts_deletes_no_other_contacts_rationale(self): + """When a user submits the Other Contacts form with other contacts selected, the application's + no other contacts rationale gets deleted""" + # Application has other contacts by default + application = completed_application(user=self.user, has_other_contacts=False) + application.no_other_contacts_rationale = "Hello!" + application.save() + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "False") + + other_contacts_form["other_contacts-has_other_contacts"] = "True" + + other_contacts_form["other_contacts-0-first_name"] = "Testy" + other_contacts_form["other_contacts-0-middle_name"] = "" + other_contacts_form["other_contacts-0-last_name"] = "McTesterson" + other_contacts_form["other_contacts-0-title"] = "Lord" + other_contacts_form["other_contacts-0-email"] = "testy@abc.org" + other_contacts_form["other_contacts-0-phone"] = "(201) 555-0123" + + # Submit the now empty form + other_contacts_form.submit() + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Verify that the no_other_contacts_rationale we saved earlier has been removed from the database + application = DomainApplication.objects.get() + self.assertEqual( + application.other_contacts.count(), + 1, + ) + + self.assertEquals( + application.no_other_contacts_rationale, + None, + ) + + 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 application's + other contacts get deleted for other contacts that exist and are not joined to other objects + """ + # Application has other contacts by default + application = completed_application(user=self.user) + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") + + other_contacts_form["other_contacts-has_other_contacts"] = "False" + + other_contacts_form["other_contacts-no_other_contacts_rationale"] = "Hello again!" + + # Submit the now empty form + other_contacts_form.submit() + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Verify that the no_other_contacts_rationale we saved earlier has been removed from the database + application = DomainApplication.objects.get() + self.assertEqual( + application.other_contacts.count(), + 0, + ) + + self.assertEquals( + application.no_other_contacts_rationale, + "Hello again!", + ) + + 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 application's + other contacts references get removed for other contacts that exist and are joined to other objects""" + # Populate the database with a domain application that + # has 1 "other contact" assigned to it + # We'll do it from scratch so we can reuse the other contact + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(555) 555 5555", + ) + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(555) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(555) 555 5557", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + submitter=you, + creator=self.user, + status="started", + ) + application.other_contacts.add(other) + + # Now let's join the other contact to another object + domain_info = DomainInformation.objects.create(creator=self.user) + domain_info.other_contacts.set([other]) + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, "True") + + other_contacts_form["other_contacts-has_other_contacts"] = "False" + + other_contacts_form["other_contacts-no_other_contacts_rationale"] = "Hello again!" + + # Submit the now empty form + other_contacts_form.submit() + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Verify that the no_other_contacts_rationale we saved earlier is no longer associated with the application + application = DomainApplication.objects.get() + self.assertEqual( + application.other_contacts.count(), + 0, + ) + + # Verify that the 'other' contact object still exists + domain_info = DomainInformation.objects.get() + self.assertEqual( + domain_info.other_contacts.count(), + 1, + ) + self.assertEqual( + domain_info.other_contacts.all()[0].first_name, + "Testy2", + ) + + self.assertEquals( + application.no_other_contacts_rationale, + "Hello again!", + ) + + def test_if_yes_no_form_is_no_then_no_other_contacts_required(self): + """Applicants with no other contacts have to give a reason.""" + other_contacts_page = self.app.get(reverse("application:other_contacts")) + other_contacts_form = other_contacts_page.forms[0] + other_contacts_form["other_contacts-has_other_contacts"] = "False" + response = other_contacts_page.forms[0].submit() + + # The textarea for no other contacts returns this error message + # Assert that it is returned, ie the no other contacts form is required + self.assertContains(response, "Rationale for no other employees is required.") + + # The first name field for other contacts returns this error message + # 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.") + + def test_if_yes_no_form_is_yes_then_other_contacts_required(self): + """Applicants with other contacts do not have to give a reason.""" + other_contacts_page = self.app.get(reverse("application:other_contacts")) + other_contacts_form = other_contacts_page.forms[0] + other_contacts_form["other_contacts-has_other_contacts"] = "True" + response = other_contacts_page.forms[0].submit() + + # The textarea for no other contacts returns this error message + # Assert that it is not returned, ie the no other contacts form is not required + self.assertNotContains(response, "Rationale for no other employees is required.") + + # The first name field for other contacts returns this error message + # Assert that it is returned, ie the contacts form is required + self.assertContains(response, "Enter the first name / given name of this contact.") + + def test_delete_other_contact(self): + """Other contacts can be deleted after being saved to database. + + This formset uses the DJANGO DELETE widget. We'll test that by setting 2 contacts on an application, + loading the form and marking one contact up for deletion.""" + # Populate the database with a domain application that + # has 2 "other contact" assigned to it + # We'll do it from scratch so we can reuse the other contact + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(201) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(201) 555 5557", + ) + other2, _ = Contact.objects.get_or_create( + first_name="Testy3", + last_name="Tester3", + title="Another Tester", + email="testy3@town.com", + phone="(201) 555 5557", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + submitter=you, + creator=self.user, + status="started", + ) + application.other_contacts.add(other) + application.other_contacts.add(other2) + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + + # Minimal check to ensure the form is loaded with both other contacts + self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") + self.assertEqual(other_contacts_form["other_contacts-1-first_name"].value, "Testy3") + + # Mark the first dude for deletion + other_contacts_form.set("other_contacts-0-DELETE", "on") + + # Submit the form + other_contacts_form.submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Verify that the first dude was deleted + application = DomainApplication.objects.get() + self.assertEqual(application.other_contacts.count(), 1) + self.assertEqual(application.other_contacts.first().first_name, "Testy3") + + def test_delete_other_contact_does_not_allow_zero_contacts(self): + """Delete Other Contact does not allow submission with zero contacts.""" + # Populate the database with a domain application that + # has 1 "other contact" assigned to it + # We'll do it from scratch so we can reuse the other contact + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(201) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(201) 555 5557", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + submitter=you, + creator=self.user, + status="started", + ) + application.other_contacts.add(other) + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") + + # Mark the first dude for deletion + other_contacts_form.set("other_contacts-0-DELETE", "on") + + # Submit the form + other_contacts_form.submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Verify that the contact was not deleted + application = DomainApplication.objects.get() + self.assertEqual(application.other_contacts.count(), 1) + self.assertEqual(application.other_contacts.first().first_name, "Testy2") + + def test_delete_other_contact_sets_visible_empty_form_as_required_after_failed_submit(self): + """When you: + 1. add an empty contact, + 2. delete existing contacts, + 3. then submit, + The forms on page reload shows all the required fields and their errors.""" + + # Populate the database with a domain application that + # has 1 "other contact" assigned to it + # We'll do it from scratch so we can reuse the other contact + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(201) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(201) 555 5557", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + submitter=you, + creator=self.user, + status="started", + ) + application.other_contacts.add(other) + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") + + # Set total forms to 2 indicating an additional formset was added. + # Submit no data though for the second formset. + # Set the first formset to be deleted. + other_contacts_form["other_contacts-TOTAL_FORMS"] = "2" + other_contacts_form.set("other_contacts-0-DELETE", "on") + + response = other_contacts_form.submit() + + # Assert that the response presents errors to the user, including to + # Enter the first name ... + self.assertContains(response, "Enter the first name / given name of this contact.") + + def test_edit_other_contact_in_place(self): + """When you: + 1. edit an existing contact which is not joined to another model, + 2. then submit, + The application is linked to the existing contact, and the existing contact updated.""" + + # Populate the database with a domain application that + # has 1 "other contact" assigned to it + # We'll do it from scratch + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(201) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(201) 555 5557", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + submitter=you, + creator=self.user, + status="started", + ) + application.other_contacts.add(other) + + # other_contact_pk is the initial pk of the other contact. set it before update + # to be able to verify after update that the same contact object is in place + other_contact_pk = other.id + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2") + + # update the first name of the contact + other_contacts_form["other_contacts-0-first_name"] = "Testy3" + + # Submit the updated form + other_contacts_form.submit() + + application.refresh_from_db() + + # assert that the Other Contact is updated "in place" + other_contact = application.other_contacts.all()[0] + self.assertEquals(other_contact_pk, other_contact.id) + self.assertEquals("Testy3", other_contact.first_name) + + def test_edit_other_contact_creates_new(self): + """When you: + 1. edit an existing contact which IS joined to another model, + 2. then submit, + The application is linked to a new contact, and the new contact is updated.""" + + # Populate the database with a domain application that + # has 1 "other contact" assigned to it, the other contact is also + # the authorizing official initially + # We'll do it from scratch + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(201) 555 5556", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + submitter=you, + creator=self.user, + status="started", + ) + application.other_contacts.add(ao) + + # other_contact_pk is the initial pk of the other contact. set it before update + # to be able to verify after update that the ao contact is still in place + # and not updated, and that the new contact has a new id + other_contact_pk = ao.id + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_page = self.app.get(reverse("application:other_contacts")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + other_contacts_form = other_contacts_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy") + + # update the first name of the contact + other_contacts_form["other_contacts-0-first_name"] = "Testy2" + + # Submit the updated form + other_contacts_form.submit() + + application.refresh_from_db() + + # assert that other contact info is updated, and that a new Contact + # is created for the other contact + other_contact = application.other_contacts.all()[0] + self.assertNotEquals(other_contact_pk, other_contact.id) + self.assertEquals("Testy2", other_contact.first_name) + # assert that the authorizing official is not updated + authorizing_official = application.authorizing_official + self.assertEquals("Testy", authorizing_official.first_name) + + def test_edit_authorizing_official_in_place(self): + """When you: + 1. edit an authorizing official which is not joined to another model, + 2. then submit, + The application is linked to the existing ao, and the ao updated.""" + + # Populate the database with a domain application that + # has an authorizing_official (ao) + # We'll do it from scratch + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + creator=self.user, + status="started", + ) + + # ao_pk is the initial pk of the Authorizing Official. set it before update + # to be able to verify after update that the same Contact object is in place + ao_pk = ao.id + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + ao_page = self.app.get(reverse("application:authorizing_official")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + ao_form = ao_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(ao_form["authorizing_official-first_name"].value, "Testy") + + # update the first name of the contact + ao_form["authorizing_official-first_name"] = "Testy2" + + # Submit the updated form + ao_form.submit() + + application.refresh_from_db() + + # assert AO is updated "in place" + updated_ao = application.authorizing_official + self.assertEquals(ao_pk, updated_ao.id) + self.assertEquals("Testy2", updated_ao.first_name) + + def test_edit_authorizing_official_creates_new(self): + """When you: + 1. edit an existing authorizing official which IS joined to another model, + 2. then submit, + The application is linked to a new Contact, and the new Contact is updated.""" + + # Populate the database with a domain application that + # has authorizing official assigned to it, the authorizing offical is also + # an other contact initially + # We'll do it from scratch + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + creator=self.user, + status="started", + ) + application.other_contacts.add(ao) + + # ao_pk is the initial pk of the authorizing official. set it before update + # to be able to verify after update that the other contact is still in place + # and not updated, and that the new ao has a new id + ao_pk = ao.id + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + ao_page = self.app.get(reverse("application:authorizing_official")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + ao_form = ao_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(ao_form["authorizing_official-first_name"].value, "Testy") + + # update the first name of the contact + ao_form["authorizing_official-first_name"] = "Testy2" + + # Submit the updated form + ao_form.submit() + + application.refresh_from_db() + + # assert that the other contact is not updated + other_contacts = application.other_contacts.all() + other_contact = other_contacts[0] + self.assertEquals(ao_pk, other_contact.id) + self.assertEquals("Testy", other_contact.first_name) + # assert that the authorizing official is updated + authorizing_official = application.authorizing_official + self.assertEquals("Testy2", authorizing_official.first_name) + + def test_edit_submitter_in_place(self): + """When you: + 1. edit a submitter (your contact) which is not joined to another model, + 2. then submit, + The application is linked to the existing submitter, and the submitter updated.""" + + # Populate the database with a domain application that + # has a submitter + # We'll do it from scratch + you, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + submitter=you, + creator=self.user, + status="started", + ) + + # submitter_pk is the initial pk of the submitter. set it before update + # to be able to verify after update that the same contact object is in place + submitter_pk = you.id + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + your_contact_page = self.app.get(reverse("application:your_contact")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + your_contact_form = your_contact_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(your_contact_form["your_contact-first_name"].value, "Testy") + + # update the first name of the contact + your_contact_form["your_contact-first_name"] = "Testy2" + + # Submit the updated form + your_contact_form.submit() + + application.refresh_from_db() + + updated_submitter = application.submitter + self.assertEquals(submitter_pk, updated_submitter.id) + self.assertEquals("Testy2", updated_submitter.first_name) + + def test_edit_submitter_creates_new(self): + """When you: + 1. edit an existing your contact which IS joined to another model, + 2. then submit, + The application is linked to a new Contact, and the new Contact is updated.""" + + # Populate the database with a domain application that + # has submitter assigned to it, the submitter is also + # an other contact initially + # We'll do it from scratch + submitter, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(201) 555 5555", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + submitter=submitter, + creator=self.user, + status="started", + ) + application.other_contacts.add(submitter) + + # submitter_pk is the initial pk of the your contact. set it before update + # to be able to verify after update that the other contact is still in place + # and not updated, and that the new submitter has a new id + submitter_pk = submitter.id + + # prime the form by visiting /edit + self.app.get(reverse("edit-application", kwargs={"id": application.pk})) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + your_contact_page = self.app.get(reverse("application:your_contact")) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + your_contact_form = your_contact_page.forms[0] + + # Minimal check to ensure the form is loaded + self.assertEqual(your_contact_form["your_contact-first_name"].value, "Testy") + + # update the first name of the contact + your_contact_form["your_contact-first_name"] = "Testy2" + + # Submit the updated form + your_contact_form.submit() + + application.refresh_from_db() + + # assert that the other contact is not updated + other_contacts = application.other_contacts.all() + other_contact = other_contacts[0] + self.assertEquals(submitter_pk, other_contact.id) + self.assertEquals("Testy", other_contact.first_name) + # assert that the submitter is updated + submitter = application.submitter + self.assertEquals("Testy2", submitter.first_name) + + def test_application_about_your_organiztion_interstate(self): + """Special districts have to answer an additional question.""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + contact_page = type_result.follow() + + self.assertContains(contact_page, self.TITLES[Step.ABOUT_YOUR_ORGANIZATION]) + + def test_application_tribal_government(self): + """Tribal organizations have to answer an additional question.""" + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + # the tribal government page comes immediately afterwards + self.assertIn("/tribal_government", type_result.headers["Location"]) + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + tribal_government_page = type_result.follow() + + # and the step is on the sidebar list. + self.assertContains(tribal_government_page, self.TITLES[Step.TRIBAL_GOVERNMENT]) + + def test_application_ao_dynamic_text(self): + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + # ---- TYPE PAGE ---- + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = "federal" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + + # ---- FEDERAL BRANCH PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_page = type_result.follow() + federal_form = federal_page.forms[0] + federal_form["organization_federal-federal_type"] = "executive" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_result = federal_form.submit() + + # ---- ORG CONTACT PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + org_contact_page = federal_result.follow() + 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"] = "General Services Administration" + org_contact_form["organization_contact-organization_name"] = "Testorg" + org_contact_form["organization_contact-address_line1"] = "address 1" + org_contact_form["organization_contact-address_line2"] = "address 2" + org_contact_form["organization_contact-city"] = "NYC" + org_contact_form["organization_contact-state_territory"] = "NY" + org_contact_form["organization_contact-zipcode"] = "10002" + org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + org_contact_result = org_contact_form.submit() + + # ---- AO CONTACT PAGE ---- + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_page = org_contact_result.follow() + self.assertContains(ao_page, "Executive branch federal agencies") + + # Go back to organization type page and change type + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_page.click(str(self.TITLES["organization_type"]), index=0) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_form["organization_type-organization_type"] = "city" + type_result = type_form.submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + election_page = type_result.follow() + + # Go back to AO page and test the dynamic text changed + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_page = election_page.click(str(self.TITLES["authorizing_official"]), index=0) + self.assertContains(ao_page, "Domain requests from cities") + + def test_application_dotgov_domain_dynamic_text(self): + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + # ---- TYPE PAGE ---- + type_form = type_page.forms[0] + type_form["organization_type-organization_type"] = "federal" + + # test next button + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_result = type_form.submit() + + # ---- FEDERAL BRANCH PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_page = type_result.follow() + federal_form = federal_page.forms[0] + federal_form["organization_federal-federal_type"] = "executive" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + federal_result = federal_form.submit() + + # ---- ORG CONTACT PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + org_contact_page = federal_result.follow() + 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"] = "General Services Administration" + org_contact_form["organization_contact-organization_name"] = "Testorg" + org_contact_form["organization_contact-address_line1"] = "address 1" + org_contact_form["organization_contact-address_line2"] = "address 2" + org_contact_form["organization_contact-city"] = "NYC" + org_contact_form["organization_contact-state_territory"] = "NY" + org_contact_form["organization_contact-zipcode"] = "10002" + org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + org_contact_result = org_contact_form.submit() + + # ---- AO CONTACT PAGE ---- + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_page = org_contact_result.follow() + + # ---- AUTHORIZING OFFICIAL PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_page = org_contact_result.follow() + ao_form = ao_page.forms[0] + ao_form["authorizing_official-first_name"] = "Testy ATO" + ao_form["authorizing_official-last_name"] = "Tester ATO" + ao_form["authorizing_official-title"] = "Chief Tester" + ao_form["authorizing_official-email"] = "testy@town.com" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_result = ao_form.submit() + + # ---- CURRENT SITES PAGE ---- + # Follow the redirect to the next form page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + current_sites_page = ao_result.follow() + current_sites_form = current_sites_page.forms[0] + current_sites_form["current_sites-0-website"] = "www.city.com" + + # test saving the page + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + current_sites_result = current_sites_form.submit() + + # ---- DOTGOV DOMAIN PAGE ---- + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + dotgov_page = current_sites_result.follow() + + self.assertContains(dotgov_page, "medicare.gov") + + # Go back to organization type page and change type + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + dotgov_page.click(str(self.TITLES["organization_type"]), index=0) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_form["organization_type-organization_type"] = "city" + type_result = type_form.submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + election_page = type_result.follow() + + # Go back to dotgov domain page to test the dynamic text changed + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + dotgov_page = election_page.click(str(self.TITLES["dotgov_domain"]), index=0) + self.assertContains(dotgov_page, "CityofEudoraKS.gov") + self.assertNotContains(dotgov_page, "medicare.gov") + + def test_application_formsets(self): + """Users are able to add more than one of some fields.""" + current_sites_page = self.app.get(reverse("application:current_sites")) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + # fill in the form field + current_sites_form = current_sites_page.forms[0] + self.assertIn("current_sites-0-website", current_sites_form.fields) + self.assertNotIn("current_sites-1-website", current_sites_form.fields) + current_sites_form["current_sites-0-website"] = "https://example.com" + + # click "Add another" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + current_sites_result = current_sites_form.submit("submit_button", value="save") + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + current_sites_form = current_sites_result.follow().forms[0] + + # verify that there are two form fields + value = current_sites_form["current_sites-0-website"].value + self.assertEqual(value, "https://example.com") + self.assertIn("current_sites-1-website", current_sites_form.fields) + # and it is correctly referenced in the ManyToOne relationship + application = DomainApplication.objects.get() # there's only one + self.assertEqual( + application.current_websites.filter(website="https://example.com").count(), + 1, + ) + + @skip("WIP") + def test_application_edit_restore(self): + """ + Test that a previously saved application is available at the /edit endpoint. + """ + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(555) 555 5555", + ) + domain, _ = Domain.objects.get_or_create(name="city.gov") + alt, _ = Website.objects.get_or_create(website="city1.gov") + current, _ = Website.objects.get_or_create(website="city.com") + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="testy-admin@town.com", + phone="(555) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(555) 555 5557", + ) + application, _ = DomainApplication.objects.get_or_create( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + anything_else="No", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + requested_domain=domain, + submitter=you, + creator=self.user, + ) + application.other_contacts.add(other) + application.current_websites.add(current) + application.alternative_domains.add(alt) + + # prime the form by visiting /edit + url = reverse("edit-application", kwargs={"id": application.pk}) + response = self.client.get(url) + + # TODO: this is a sketch of each page in the wizard which needs to be tested + # Django does not have tools sufficient for real end to end integration testing + # (for example, USWDS moves radio buttons off screen and replaces them with + # CSS styled "fakes" -- Django cannot determine if those are visually correct) + # -- the best that can/should be done here is to ensure the correct values + # are being passed to the templating engine + + url = reverse("application:organization_type") + response = self.client.get(url, follow=True) + self.assertContains(response, "") + # choices = response.context['wizard']['form']['organization_type'].subwidgets + # radio = [ x for x in choices if x.data["value"] == "federal" ][0] + # checked = radio.data["selected"] + # self.assertTrue(checked) + + # url = reverse("application:organization_federal") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:organization_contact") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:authorizing_official") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:current_sites") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:dotgov_domain") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:purpose") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:your_contact") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:other_contacts") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:other_contacts") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:security_email") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:anything_else") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + # url = reverse("application:requirements") + # self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # page = self.app.get(url) + # self.assertNotContains(page, "VALUE") + + def test_long_org_name_in_application(self): + """ + Make sure the long name is displaying in the application form, + org step + """ + intro_page = self.app.get(reverse("application:")) + # django-webtest does not handle cookie-based sessions well because it keeps + # resetting the session key on each new request, thus destroying the concept + # of a "session". We are going to do it manually, saving the session ID here + # and then setting the cookie on each request. + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + intro_form = intro_page.forms[0] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + intro_result = intro_form.submit() + + # follow first redirect + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + type_page = intro_result.follow() + + self.assertContains(type_page, "Federal: an agency of the U.S. government") + + def test_submit_modal_no_domain_text_fallback(self): + """When user clicks on submit your domain request and the requested domain + is null (possible through url direct access to the review page), present + fallback copy in the modal's header. + + NOTE: This may be a moot point if we implement a more solid pattern in the + future, like not a submit action at all on the review page.""" + + review_page = self.app.get(reverse("application:review")) + self.assertContains(review_page, "toggle-submit-domain-request") + self.assertContains(review_page, "You are about to submit an incomplete request") + + +class DomainApplicationTestDifferentStatuses(TestWithUser, WebTest): + def setUp(self): + super().setUp() + self.app.set_user(self.user.username) + self.client.force_login(self.user) + + def test_application_status(self): + """Checking application status page""" + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) + application.save() + + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + # click the "Manage" link + detail_page = home_page.click("Manage", index=0) + self.assertContains(detail_page, "city.gov") + self.assertContains(detail_page, "city1.gov") + self.assertContains(detail_page, "Chief Tester") + self.assertContains(detail_page, "testy@town.com") + self.assertContains(detail_page, "Admin Tester") + self.assertContains(detail_page, "Status:") + + def test_application_status_with_ineligible_user(self): + """Checking application status page whith a blocked user. + The user should still have access to view.""" + self.user.status = "ineligible" + self.user.save() + + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) + application.save() + + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + # click the "Manage" link + detail_page = home_page.click("Manage", index=0) + self.assertContains(detail_page, "city.gov") + self.assertContains(detail_page, "Chief Tester") + self.assertContains(detail_page, "testy@town.com") + self.assertContains(detail_page, "Admin Tester") + self.assertContains(detail_page, "Status:") + + def test_application_withdraw(self): + """Checking application status page""" + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) + application.save() + + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + # click the "Manage" link + detail_page = home_page.click("Manage", index=0) + self.assertContains(detail_page, "city.gov") + self.assertContains(detail_page, "city1.gov") + self.assertContains(detail_page, "Chief Tester") + self.assertContains(detail_page, "testy@town.com") + self.assertContains(detail_page, "Admin Tester") + self.assertContains(detail_page, "Status:") + # click the "Withdraw request" button + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + withdraw_page = detail_page.click("Withdraw request") + self.assertContains(withdraw_page, "Withdraw request for") + home_page = withdraw_page.click("Withdraw request") + # confirm that it has redirected, and the status has been updated to withdrawn + self.assertRedirects( + home_page, + "/", + status_code=302, + target_status_code=200, + fetch_redirect_response=True, + ) + home_page = self.app.get("/") + self.assertContains(home_page, "Withdrawn") + + def test_application_withdraw_no_permissions(self): + """Can't withdraw applications as a restricted user.""" + self.user.status = User.RESTRICTED + self.user.save() + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) + application.save() + + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + # click the "Manage" link + detail_page = home_page.click("Manage", index=0) + self.assertContains(detail_page, "city.gov") + self.assertContains(detail_page, "city1.gov") + self.assertContains(detail_page, "Chief Tester") + self.assertContains(detail_page, "testy@town.com") + self.assertContains(detail_page, "Admin Tester") + self.assertContains(detail_page, "Status:") + # Restricted user trying to withdraw results in 403 error + with less_console_noise(): + for url_name in [ + "application-withdraw-confirmation", + "application-withdrawn", + ]: + with self.subTest(url_name=url_name): + page = self.client.get(reverse(url_name, kwargs={"pk": application.pk})) + self.assertEqual(page.status_code, 403) + + def test_application_status_no_permissions(self): + """Can't access applications without being the creator.""" + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) + other_user = User() + other_user.save() + application.creator = other_user + application.save() + + # PermissionDeniedErrors make lots of noise in test output + with less_console_noise(): + for url_name in [ + "application-status", + "application-withdraw-confirmation", + "application-withdrawn", + ]: + with self.subTest(url_name=url_name): + page = self.client.get(reverse(url_name, kwargs={"pk": application.pk})) + self.assertEqual(page.status_code, 403) + + def test_approved_application_not_in_active_requests(self): + """An approved application is not shown in the Active + Requests table on home.html.""" + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED, user=self.user) + application.save() + + home_page = self.app.get("/") + # This works in our test environment because creating + # an approved application here does not generate a + # domain object, so we do not expect to see 'city.gov' + # in either the Domains or Requests tables. + self.assertNotContains(home_page, "city.gov") diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py new file mode 100644 index 000000000..c9422e700 --- /dev/null +++ b/src/registrar/tests/test_views_domain.py @@ -0,0 +1,1436 @@ +from unittest import skip +from unittest.mock import MagicMock, ANY, patch + +from django.conf import settings +from django.urls import reverse +from django.contrib.auth import get_user_model + +from .common import MockSESClient, create_user # type: ignore +from django_webtest import WebTest # type: ignore +import boto3_mocking # type: ignore + +from registrar.utility.errors import ( + NameserverError, + NameserverErrorCodes, + SecurityEmailError, + SecurityEmailErrorCodes, + GenericError, + GenericErrorCodes, + DsDataError, + DsDataErrorCodes, +) + +from registrar.models import ( + DomainApplication, + Domain, + DomainInformation, + DomainInvitation, + Contact, + PublicContact, + Host, + HostIP, + UserDomainRole, + User, +) +from datetime import date, datetime, timedelta +from django.utils import timezone + +from .common import less_console_noise +from .test_views import TestWithUser + +import logging + +logger = logging.getLogger(__name__) + + +class TestWithDomainPermissions(TestWithUser): + def setUp(self): + super().setUp() + self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") + self.domain_with_ip, _ = Domain.objects.get_or_create(name="nameserverwithip.gov") + self.domain_just_nameserver, _ = Domain.objects.get_or_create(name="justnameserver.com") + self.domain_no_information, _ = Domain.objects.get_or_create(name="noinformation.gov") + self.domain_on_hold, _ = Domain.objects.get_or_create( + name="on-hold.gov", + state=Domain.State.ON_HOLD, + expiration_date=timezone.make_aware( + datetime.combine(date.today() + timedelta(days=1), datetime.min.time()) + ), + ) + self.domain_deleted, _ = Domain.objects.get_or_create( + name="deleted.gov", + state=Domain.State.DELETED, + expiration_date=timezone.make_aware( + datetime.combine(date.today() + timedelta(days=1), datetime.min.time()) + ), + ) + + self.domain_dsdata, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") + self.domain_multdsdata, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov") + # We could simply use domain (igorville) but this will be more readable in tests + # that inherit this setUp + self.domain_dnssec_none, _ = Domain.objects.get_or_create(name="dnssec-none.gov") + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dsdata) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_multdsdata) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dnssec_none) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_ip) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_just_nameserver) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_on_hold) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_deleted) + + self.role, _ = UserDomainRole.objects.get_or_create( + user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER + ) + + UserDomainRole.objects.get_or_create( + user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.MANAGER + ) + UserDomainRole.objects.get_or_create( + user=self.user, + domain=self.domain_multdsdata, + role=UserDomainRole.Roles.MANAGER, + ) + UserDomainRole.objects.get_or_create( + user=self.user, + domain=self.domain_dnssec_none, + role=UserDomainRole.Roles.MANAGER, + ) + UserDomainRole.objects.get_or_create( + user=self.user, + domain=self.domain_with_ip, + role=UserDomainRole.Roles.MANAGER, + ) + UserDomainRole.objects.get_or_create( + user=self.user, + domain=self.domain_just_nameserver, + role=UserDomainRole.Roles.MANAGER, + ) + UserDomainRole.objects.get_or_create( + user=self.user, domain=self.domain_on_hold, role=UserDomainRole.Roles.MANAGER + ) + UserDomainRole.objects.get_or_create( + user=self.user, domain=self.domain_deleted, role=UserDomainRole.Roles.MANAGER + ) + + def tearDown(self): + try: + UserDomainRole.objects.all().delete() + if hasattr(self.domain, "contacts"): + self.domain.contacts.all().delete() + DomainApplication.objects.all().delete() + DomainInformation.objects.all().delete() + PublicContact.objects.all().delete() + HostIP.objects.all().delete() + Host.objects.all().delete() + Domain.objects.all().delete() + UserDomainRole.objects.all().delete() + except ValueError: # pass if already deleted + pass + super().tearDown() + + +class TestDomainPermissions(TestWithDomainPermissions): + def test_not_logged_in(self): + """Not logged in gets a redirect to Login.""" + for view_name in [ + "domain", + "domain-users", + "domain-users-add", + "domain-dns-nameservers", + "domain-org-name-address", + "domain-authorizing-official", + "domain-your-contact-information", + "domain-security-email", + ]: + with self.subTest(view_name=view_name): + response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) + self.assertEqual(response.status_code, 302) + + def test_no_domain_role(self): + """Logged in but no role gets 403 Forbidden.""" + self.client.force_login(self.user) + self.role.delete() # user no longer has a role on this domain + + for view_name in [ + "domain", + "domain-users", + "domain-users-add", + "domain-dns-nameservers", + "domain-org-name-address", + "domain-authorizing-official", + "domain-your-contact-information", + "domain-security-email", + ]: + with self.subTest(view_name=view_name): + with less_console_noise(): + response = self.client.get(reverse(view_name, kwargs={"pk": self.domain.id})) + self.assertEqual(response.status_code, 403) + + def test_domain_pages_blocked_for_on_hold_and_deleted(self): + """Test that the domain pages are blocked for on hold and deleted domains""" + + self.client.force_login(self.user) + for view_name in [ + "domain-users", + "domain-users-add", + "domain-dns", + "domain-dns-nameservers", + "domain-dns-dnssec", + "domain-dns-dnssec-dsdata", + "domain-org-name-address", + "domain-authorizing-official", + "domain-your-contact-information", + "domain-security-email", + ]: + for domain in [ + self.domain_on_hold, + self.domain_deleted, + ]: + with self.subTest(view_name=view_name, domain=domain): + with less_console_noise(): + response = self.client.get(reverse(view_name, kwargs={"pk": domain.id})) + self.assertEqual(response.status_code, 403) + + +class TestDomainOverview(TestWithDomainPermissions, WebTest): + def setUp(self): + super().setUp() + self.app.set_user(self.user.username) + self.client.force_login(self.user) + + +class TestDomainDetail(TestDomainOverview): + @skip("Assertion broke for no reason, why? Need to fix") + def test_domain_detail_link_works(self): + home_page = self.app.get("/") + logger.info(f"This is the value of home_page: {home_page}") + self.assertContains(home_page, "igorville.gov") + # click the "Edit" link + detail_page = home_page.click("Manage", index=0) + self.assertContains(detail_page, "igorville.gov") + self.assertContains(detail_page, "Status") + + def test_unknown_domain_does_not_show_as_expired_on_homepage(self): + """An UNKNOWN domain does not show as expired on the homepage. + It shows as 'DNS needed'""" + # At the time of this test's writing, there are 6 UNKNOWN domains inherited + # from constructors. Let's reset. + with less_console_noise(): + Domain.objects.all().delete() + UserDomainRole.objects.all().delete() + self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") + home_page = self.app.get("/") + self.assertNotContains(home_page, "igorville.gov") + self.role, _ = UserDomainRole.objects.get_or_create( + user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER + ) + home_page = self.app.get("/") + self.assertContains(home_page, "igorville.gov") + igorville = Domain.objects.get(name="igorville.gov") + self.assertEquals(igorville.state, Domain.State.UNKNOWN) + self.assertNotContains(home_page, "Expired") + self.assertContains(home_page, "DNS needed") + + def test_unknown_domain_does_not_show_as_expired_on_detail_page(self): + """An UNKNOWN domain does not show as expired on the detail page. + It shows as 'DNS needed'""" + # At the time of this test's writing, there are 6 UNKNOWN domains inherited + # from constructors. Let's reset. + with less_console_noise(): + Domain.objects.all().delete() + UserDomainRole.objects.all().delete() + + self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + self.role, _ = UserDomainRole.objects.get_or_create( + user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER + ) + + home_page = self.app.get("/") + self.assertContains(home_page, "igorville.gov") + igorville = Domain.objects.get(name="igorville.gov") + self.assertEquals(igorville.state, Domain.State.UNKNOWN) + detail_page = home_page.click("Manage", index=0) + self.assertNotContains(detail_page, "Expired") + + self.assertContains(detail_page, "DNS needed") + + def test_domain_detail_blocked_for_ineligible_user(self): + """We could easily duplicate this test for all domain management + views, but a single url test should be solid enough since all domain + management pages share the same permissions class""" + with less_console_noise(): + self.user.status = User.RESTRICTED + self.user.save() + home_page = self.app.get("/") + self.assertContains(home_page, "igorville.gov") + response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) + self.assertEqual(response.status_code, 403) + + def test_domain_detail_allowed_for_on_hold(self): + """Test that the domain overview page displays for on hold domain""" + with less_console_noise(): + home_page = self.app.get("/") + self.assertContains(home_page, "on-hold.gov") + + # View domain overview page + detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_on_hold.id})) + self.assertNotContains(detail_page, "Edit") + + def test_domain_detail_see_just_nameserver(self): + with less_console_noise(): + home_page = self.app.get("/") + self.assertContains(home_page, "justnameserver.com") + + # View nameserver on Domain Overview page + detail_page = self.app.get(reverse("domain", kwargs={"pk": self.domain_just_nameserver.id})) + + self.assertContains(detail_page, "justnameserver.com") + self.assertContains(detail_page, "ns1.justnameserver.com") + self.assertContains(detail_page, "ns2.justnameserver.com") + + def test_domain_detail_see_nameserver_and_ip(self): + with less_console_noise(): + home_page = self.app.get("/") + self.assertContains(home_page, "nameserverwithip.gov") + + # View nameserver on Domain Overview page + detail_page = self.app.get(reverse("domain", kwargs={"pk": self.domain_with_ip.id})) + + self.assertContains(detail_page, "nameserverwithip.gov") + + self.assertContains(detail_page, "ns1.nameserverwithip.gov") + self.assertContains(detail_page, "ns2.nameserverwithip.gov") + self.assertContains(detail_page, "ns3.nameserverwithip.gov") + # Splitting IP addresses bc there is odd whitespace and can't strip text + self.assertContains(detail_page, "(1.2.3.4,") + self.assertContains(detail_page, "2.3.4.5)") + + def test_domain_detail_with_no_information_or_application(self): + """Test that domain management page returns 200 and displays error + when no domain information or domain application exist""" + with less_console_noise(): + # have to use staff user for this test + staff_user = create_user() + # staff_user.save() + self.client.force_login(staff_user) + + # need to set the analyst_action and analyst_action_location + # in the session to emulate user clicking Manage Domain + # in the admin interface + session = self.client.session + session["analyst_action"] = "foo" + session["analyst_action_location"] = self.domain_no_information.id + session.save() + + detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_no_information.id})) + + self.assertContains(detail_page, "noinformation.gov") + self.assertContains(detail_page, "Domain missing domain information") + + +class TestDomainManagers(TestDomainOverview): + def tearDown(self): + """Ensure that the user has its original permissions""" + super().tearDown() + self.user.is_staff = False + self.user.save() + + def test_domain_managers(self): + response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) + self.assertContains(response, "Domain managers") + + def test_domain_managers_add_link(self): + """Button to get to user add page works.""" + management_page = self.app.get(reverse("domain-users", kwargs={"pk": self.domain.id})) + add_page = management_page.click("Add a domain manager") + self.assertContains(add_page, "Add a domain manager") + + def test_domain_user_add(self): + response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + self.assertContains(response, "Add a domain manager") + + @boto3_mocking.patching + def test_domain_user_add_form(self): + """Adding an existing user works.""" + other_user, _ = get_user_model().objects.get_or_create(email="mayor@igorville.gov") + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + add_page.form["email"] = "mayor@igorville.gov" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + success_result = add_page.form.submit() + + self.assertEqual(success_result.status_code, 302) + self.assertEqual( + success_result["Location"], + reverse("domain-users", kwargs={"pk": self.domain.id}), + ) + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_page = success_result.follow() + self.assertContains(success_page, "mayor@igorville.gov") + + @boto3_mocking.patching + def test_domain_invitation_created(self): + """Add user on a nonexistent email creates an invitation. + + Adding a non-existent user sends an email as a side-effect, so mock + out the boto3 SES email sending here. + """ + # make sure there is no user with this email + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + success_result = add_page.form.submit() + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_page = success_result.follow() + + self.assertContains(success_page, email_address) + self.assertContains(success_page, "Cancel") # link to cancel invitation + self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) + + @boto3_mocking.patching + def test_domain_invitation_created_for_caps_email(self): + """Add user on a nonexistent email with CAPS creates an invitation to lowercase email. + + Adding a non-existent user sends an email as a side-effect, so mock + out the boto3 SES email sending here. + """ + # make sure there is no user with this email + email_address = "mayor@igorville.gov" + caps_email_address = "MAYOR@igorville.gov" + User.objects.filter(email=email_address).delete() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = caps_email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + success_result = add_page.form.submit() + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_page = success_result.follow() + + self.assertContains(success_page, email_address) + self.assertContains(success_page, "Cancel") # link to cancel invitation + self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) + + @boto3_mocking.patching + def test_domain_invitation_email_sent(self): + """Inviting a non-existent user sends them an email.""" + # make sure there is no user with this email + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + mock_client = MagicMock() + mock_client_instance = mock_client.return_value + 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})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() + + # check the mock instance to see if `send_email` was called right + mock_client_instance.send_email.assert_called_once_with( + FromEmailAddress=settings.DEFAULT_FROM_EMAIL, + Destination={"ToAddresses": [email_address]}, + Content=ANY, + ) + + @boto3_mocking.patching + 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.""" + # make sure there is no user with this email + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + mock_client = MagicMock() + mock_client_instance = mock_client.return_value + + 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})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() + + # check the mock instance to see if `send_email` was called right + mock_client_instance.send_email.assert_called_once_with( + FromEmailAddress=settings.DEFAULT_FROM_EMAIL, + Destination={"ToAddresses": [email_address]}, + Content=ANY, + ) + + # Check the arguments passed to send_email method + _, kwargs = mock_client_instance.send_email.call_args + + # Extract the email content, and check that the message is as we expect + email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] + self.assertIn("info@example.com", email_content) + + # Check that the requestors first/last name do not exist + self.assertNotIn("First", email_content) + self.assertNotIn("Last", email_content) + self.assertNotIn("First Last", email_content) + + @boto3_mocking.patching + def test_domain_invitation_email_has_email_as_requestor(self): + """Inviting a user sends them an email, with email as the name.""" + # Create a fake user object + email_address = "mayor@igorville.gov" + User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com") + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + mock_client = MagicMock() + mock_client_instance = mock_client.return_value + + 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})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() + + # check the mock instance to see if `send_email` was called right + mock_client_instance.send_email.assert_called_once_with( + FromEmailAddress=settings.DEFAULT_FROM_EMAIL, + Destination={"ToAddresses": [email_address]}, + Content=ANY, + ) + + # Check the arguments passed to send_email method + _, kwargs = mock_client_instance.send_email.call_args + + # Extract the email content, and check that the message is as we expect + email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] + self.assertIn("info@example.com", email_content) + + # Check that the requestors first/last name do not exist + self.assertNotIn("First", email_content) + self.assertNotIn("Last", email_content) + self.assertNotIn("First Last", email_content) + + @boto3_mocking.patching + def test_domain_invitation_email_has_email_as_requestor_staff(self): + """Inviting a user sends them an email, with email as the name.""" + # Create a fake user object + email_address = "mayor@igorville.gov" + User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com") + + # Make sure the user is staff + self.user.is_staff = True + self.user.save() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + mock_client = MagicMock() + mock_client_instance = mock_client.return_value + + 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})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit() + + # check the mock instance to see if `send_email` was called right + mock_client_instance.send_email.assert_called_once_with( + FromEmailAddress=settings.DEFAULT_FROM_EMAIL, + Destination={"ToAddresses": [email_address]}, + Content=ANY, + ) + + # Check the arguments passed to send_email method + _, kwargs = mock_client_instance.send_email.call_args + + # Extract the email content, and check that the message is as we expect + email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] + self.assertIn("help@get.gov", email_content) + + # Check that the requestors first/last name do not exist + self.assertNotIn("First", email_content) + self.assertNotIn("Last", email_content) + self.assertNotIn("First Last", email_content) + + @boto3_mocking.patching + def test_domain_invitation_email_displays_error_non_existent(self): + """Inviting a non existent user sends them an email, with email as the name.""" + # make sure there is no user with this email + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() + + # Give the user who is sending the email an invalid email address + self.user.email = "" + self.user.save() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + mock_client = MagicMock() + mock_error_message = MagicMock() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + 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})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit().follow() + + expected_message_content = "Can't send invitation email. No email is associated with your account." + + # Grab the message content + returned_error_message = mock_error_message.call_args[0][1] + + # Check that the message content is what we expect + self.assertEqual(expected_message_content, returned_error_message) + + @boto3_mocking.patching + def test_domain_invitation_email_displays_error(self): + """When the requesting user has no email, an error is displayed""" + # make sure there is no user with this email + # Create a fake user object + email_address = "mayor@igorville.gov" + User.objects.get_or_create(email=email_address, username="fakeuser@fakeymail.com") + + # Give the user who is sending the email an invalid email address + self.user.email = "" + self.user.save() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + mock_client = MagicMock() + + mock_error_message = MagicMock() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + 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})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + add_page.form.submit().follow() + + expected_message_content = "Can't send invitation email. No email is associated with your account." + + # Grab the message content + returned_error_message = mock_error_message.call_args[0][1] + + # Check that the message content is what we expect + self.assertEqual(expected_message_content, returned_error_message) + + def test_domain_invitation_cancel(self): + """Posting to the delete view deletes an invitation.""" + email_address = "mayor@igorville.gov" + invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) + mock_client = MockSESClient() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) + mock_client.EMAILS_SENT.clear() + with self.assertRaises(DomainInvitation.DoesNotExist): + DomainInvitation.objects.get(id=invitation.id) + + def test_domain_invitation_cancel_no_permissions(self): + """Posting to the delete view as a different user should fail.""" + email_address = "mayor@igorville.gov" + invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) + + other_user = User() + other_user.save() + self.client.force_login(other_user) + mock_client = MagicMock() + 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})) + + self.assertEqual(result.status_code, 403) + + @boto3_mocking.patching + def test_domain_invitation_flow(self): + """Send an invitation to a new user, log in and load the dashboard.""" + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() + + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + mock_client = MagicMock() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): + add_page.form.submit() + + # user was invited, create them + new_user = User.objects.create(username=email_address, email=email_address) + # log them in to `self.app` + self.app.set_user(new_user.username) + # and manually call the on each login callback + new_user.on_each_login() + + # Now load the home page and make sure our domain appears there + home_page = self.app.get(reverse("home")) + self.assertContains(home_page, self.domain.name) + + +class TestDomainNameservers(TestDomainOverview): + def test_domain_nameservers(self): + """Can load domain's nameservers page.""" + page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + self.assertContains(page, "DNS name servers") + + def test_domain_nameservers_form_submit_one_nameserver(self): + """Nameserver form submitted with one nameserver throws error. + + Uses self.app WebTest because we need to interact with forms. + """ + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form with only one nameserver, should error + # regarding required fields + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. form requires a minimum of 2 name servers + self.assertContains( + result, + "At least two name servers are required.", + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submit_subdomain_missing_ip(self): + """Nameserver form catches missing ip error on subdomain. + + Uses self.app WebTest because we need to interact with forms. + """ + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without two hosts, both subdomains, + # only one has ips + nameservers_page.form["form-1-server"] = "ns2.igorville.gov" + + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. subdomain missing an ip + self.assertContains( + result, + str(NameserverError(code=NameserverErrorCodes.MISSING_IP)), + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submit_missing_host(self): + """Nameserver form catches error when host is missing. + + Uses self.app WebTest because we need to interact with forms. + """ + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without two hosts, both subdomains, + # only one has ips + nameservers_page.form["form-1-ip"] = "127.0.0.1" + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. nameserver has ip but missing host + self.assertContains( + result, + str(NameserverError(code=NameserverErrorCodes.MISSING_HOST)), + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submit_duplicate_host(self): + """Nameserver form catches error when host is duplicated. + + Uses self.app WebTest because we need to interact with forms. + """ + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form with duplicate host names of fake.host.com + nameservers_page.form["form-0-ip"] = "" + nameservers_page.form["form-1-server"] = "fake.host.com" + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. remove duplicate entry + self.assertContains( + result, + str(NameserverError(code=NameserverErrorCodes.DUPLICATE_HOST)), + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submit_whitespace(self): + """Nameserver form removes whitespace from ip. + + Uses self.app WebTest because we need to interact with forms. + """ + nameserver1 = "ns1.igorville.gov" + nameserver2 = "ns2.igorville.gov" + valid_ip = "1.1. 1.1" + # initial nameservers page has one server with two ips + # have to throw an error in order to test that the whitespace has been stripped from ip + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without one host and an ip with whitespace + nameservers_page.form["form-0-server"] = nameserver1 + nameservers_page.form["form-1-ip"] = valid_ip + nameservers_page.form["form-1-server"] = nameserver2 + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # form submission was a post with an ip address which has been stripped of whitespace, + # response should be a 302 to success page + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), + ) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + page = result.follow() + # in the event of a generic nameserver error from registry error, there will be a 302 + # 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") + + def test_domain_nameservers_form_submit_glue_record_not_allowed(self): + """Nameserver form catches error when IP is present + but host not subdomain. + + Uses self.app WebTest because we need to interact with forms. + """ + nameserver1 = "ns1.igorville.gov" + nameserver2 = "ns2.igorville.com" + valid_ip = "127.0.0.1" + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without two hosts, both subdomains, + # only one has ips + nameservers_page.form["form-0-server"] = nameserver1 + nameservers_page.form["form-1-server"] = nameserver2 + nameservers_page.form["form-1-ip"] = valid_ip + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. nameserver has ip but missing host + self.assertContains( + result, + str(NameserverError(code=NameserverErrorCodes.GLUE_RECORD_NOT_ALLOWED)), + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submit_invalid_ip(self): + """Nameserver form catches invalid IP on submission. + + Uses self.app WebTest because we need to interact with forms. + """ + nameserver = "ns2.igorville.gov" + invalid_ip = "123" + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without two hosts, both subdomains, + # only one has ips + nameservers_page.form["form-1-server"] = nameserver + nameservers_page.form["form-1-ip"] = invalid_ip + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. nameserver has ip but missing host + self.assertContains( + result, + str(NameserverError(code=NameserverErrorCodes.INVALID_IP, nameserver=nameserver)), + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submit_invalid_host(self): + """Nameserver form catches invalid host on submission. + + Uses self.app WebTest because we need to interact with forms. + """ + nameserver = "invalid-nameserver.gov" + valid_ip = "123.2.45.111" + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without two hosts, both subdomains, + # only one has ips + nameservers_page.form["form-1-server"] = nameserver + nameservers_page.form["form-1-ip"] = valid_ip + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # 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 + # the required field. nameserver has invalid host + self.assertContains( + result, + str(NameserverError(code=NameserverErrorCodes.INVALID_HOST, nameserver=nameserver)), + count=2, + status_code=200, + ) + + def test_domain_nameservers_form_submits_successfully(self): + """Nameserver form submits successfully with valid input. + + Uses self.app WebTest because we need to interact with forms. + """ + nameserver1 = "ns1.igorville.gov" + nameserver2 = "ns2.igorville.gov" + valid_ip = "127.0.0.1" + # initial nameservers page has one server with two ips + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # attempt to submit the form without two hosts, both subdomains, + # only one has ips + nameservers_page.form["form-0-server"] = nameserver1 + nameservers_page.form["form-1-server"] = nameserver2 + nameservers_page.form["form-1-ip"] = valid_ip + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + # form submission was a successful post, response should be a 302 + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), + ) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + page = result.follow() + self.assertContains(page, "The name servers for this domain have been updated") + + def test_domain_nameservers_form_invalid(self): + """Nameserver form does not submit with invalid data. + + Uses self.app WebTest because we need to interact with forms. + """ + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # first two nameservers are required, so if we empty one out we should + # get a form error + nameservers_page.form["form-0-server"] = "" + with less_console_noise(): # swallow logged warning message + result = nameservers_page.form.submit() + # 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, + # once around each required field. + self.assertContains( + result, + "At least two name servers are required.", + count=4, + status_code=200, + ) + + +class TestDomainAuthorizingOfficial(TestDomainOverview): + def test_domain_authorizing_official(self): + """Can load domain's authorizing official page.""" + page = self.client.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) + # once on the sidebar, once in the title + self.assertContains(page, "Authorizing official", count=2) + + def test_domain_authorizing_official_content(self): + """Authorizing official information appears on the page.""" + self.domain_information.authorizing_official = Contact(first_name="Testy") + self.domain_information.authorizing_official.save() + self.domain_information.save() + page = self.app.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) + self.assertContains(page, "Testy") + + def test_domain_edit_authorizing_official_in_place(self): + """When editing an authorizing official for domain information and AO is not + joined to any other objects""" + self.domain_information.authorizing_official = Contact( + first_name="Testy", last_name="Tester", title="CIO", email="nobody@igorville.gov" + ) + self.domain_information.authorizing_official.save() + self.domain_information.save() + ao_page = self.app.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_form = ao_page.forms[0] + self.assertEqual(ao_form["first_name"].value, "Testy") + ao_form["first_name"] = "Testy2" + # ao_pk is the initial pk of the authorizing official. set it before update + # to be able to verify after update that the same contact object is in place + ao_pk = self.domain_information.authorizing_official.id + ao_form.submit() + + # refresh domain information + self.domain_information.refresh_from_db() + self.assertEqual("Testy2", self.domain_information.authorizing_official.first_name) + self.assertEqual(ao_pk, self.domain_information.authorizing_official.id) + + def test_domain_edit_authorizing_official_creates_new(self): + """When editing an authorizing official for domain information and AO IS + joined to another object""" + # set AO and Other Contact to the same Contact object + self.domain_information.authorizing_official = Contact( + first_name="Testy", last_name="Tester", title="CIO", email="nobody@igorville.gov" + ) + self.domain_information.authorizing_official.save() + self.domain_information.save() + self.domain_information.other_contacts.add(self.domain_information.authorizing_official) + self.domain_information.save() + # load the Authorizing Official in the web form + ao_page = self.app.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + ao_form = ao_page.forms[0] + # verify the first name is "Testy" and then change it to "Testy2" + self.assertEqual(ao_form["first_name"].value, "Testy") + ao_form["first_name"] = "Testy2" + # ao_pk is the initial pk of the authorizing official. set it before update + # to be able to verify after update that the same contact object is in place + ao_pk = self.domain_information.authorizing_official.id + ao_form.submit() + + # refresh domain information + self.domain_information.refresh_from_db() + # assert that AO information is updated, and that the AO is a new Contact + self.assertEqual("Testy2", self.domain_information.authorizing_official.first_name) + self.assertNotEqual(ao_pk, self.domain_information.authorizing_official.id) + # assert that the Other Contact information is not updated and that the Other Contact + # is the original Contact object + other_contact = self.domain_information.other_contacts.all()[0] + self.assertEqual("Testy", other_contact.first_name) + self.assertEqual(ao_pk, other_contact.id) + + +class TestDomainOrganization(TestDomainOverview): + def test_domain_org_name_address(self): + """Can load domain's org name and mailing address page.""" + 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 + self.assertContains(page, "Organization name and mailing address", count=3) + + def test_domain_org_name_address_content(self): + """Org name and address information appears on the page.""" + self.domain_information.organization_name = "Town of Igorville" + self.domain_information.save() + page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) + self.assertContains(page, "Town of Igorville") + + def test_domain_org_name_address_form(self): + """Submitting changes works on the org name address page.""" + self.domain_information.organization_name = "Town of Igorville" + self.domain_information.save() + org_name_page = self.app.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + org_name_page.form["organization_name"] = "Not igorville" + org_name_page.form["city"] = "Faketown" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_result_page = org_name_page.form.submit() + self.assertEqual(success_result_page.status_code, 200) + + self.assertContains(success_result_page, "Not igorville") + self.assertContains(success_result_page, "Faketown") + + +class TestDomainContactInformation(TestDomainOverview): + def test_domain_your_contact_information(self): + """Can load domain's your contact information page.""" + page = self.client.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})) + self.assertContains(page, "Your contact information") + + def test_domain_your_contact_information_content(self): + """Logged-in user's contact information appears on the page.""" + self.user.contact.first_name = "Testy" + self.user.contact.save() + page = self.app.get(reverse("domain-your-contact-information", kwargs={"pk": self.domain.id})) + self.assertContains(page, "Testy") + + +class TestDomainSecurityEmail(TestDomainOverview): + def test_domain_security_email_existing_security_contact(self): + """Can load domain's security email page.""" + with less_console_noise(): + self.mockSendPatch = patch("registrar.models.domain.registry.send") + self.mockedSendFunction = self.mockSendPatch.start() + self.mockedSendFunction.side_effect = self.mockSend + + domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov") + # Add current user to this domain + _ = UserDomainRole(user=self.user, domain=domain_contact, role="admin").save() + page = self.client.get(reverse("domain-security-email", kwargs={"pk": domain_contact.id})) + + # Loads correctly + self.assertContains(page, "Security email") + self.assertContains(page, "security@mail.gov") + self.mockSendPatch.stop() + + def test_domain_security_email_no_security_contact(self): + """Loads a domain with no defined security email. + We should not show the default.""" + with less_console_noise(): + self.mockSendPatch = patch("registrar.models.domain.registry.send") + self.mockedSendFunction = self.mockSendPatch.start() + self.mockedSendFunction.side_effect = self.mockSend + + page = self.client.get(reverse("domain-security-email", kwargs={"pk": self.domain.id})) + + # Loads correctly + self.assertContains(page, "Security email") + self.assertNotContains(page, "dotgov@cisa.dhs.gov") + self.mockSendPatch.stop() + + def test_domain_security_email(self): + """Can load domain's security email page.""" + with less_console_noise(): + page = self.client.get(reverse("domain-security-email", kwargs={"pk": self.domain.id})) + self.assertContains(page, "Security email") + + def test_domain_security_email_form(self): + """Adding a security email works. + Uses self.app WebTest because we need to interact with forms. + """ + with less_console_noise(): + security_email_page = self.app.get(reverse("domain-security-email", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + security_email_page.form["security_email"] = "mayor@igorville.gov" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + mock_client = MagicMock() + with boto3_mocking.clients.handler_for("sesv2", mock_client): + with less_console_noise(): # swallow log warning message + result = security_email_page.form.submit() + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-security-email", kwargs={"pk": self.domain.id}), + ) + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_page = result.follow() + self.assertContains(success_page, "The security email for this domain has been updated") + + def test_domain_security_email_form_messages(self): + """ + Test against the success and error messages that are defined in the view + """ + with less_console_noise(): + p = "adminpass" + self.client.login(username="superuser", password=p) + form_data_registry_error = { + "security_email": "test@failCreate.gov", + } + form_data_contact_error = { + "security_email": "test@contactError.gov", + } + form_data_success = { + "security_email": "test@something.gov", + } + test_cases = [ + ( + "RegistryError", + form_data_registry_error, + str(GenericError(code=GenericErrorCodes.CANNOT_CONTACT_REGISTRY)), + ), + ( + "ContactError", + form_data_contact_error, + str(SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA)), + ), + ( + "RegistrySuccess", + form_data_success, + "The security email for this domain has been updated.", + ), + # Add more test cases with different scenarios here + ] + for test_name, data, expected_message in test_cases: + response = self.client.post( + reverse("domain-security-email", kwargs={"pk": self.domain.id}), + data=data, + follow=True, + ) + # Check the response status code, content, or any other relevant assertions + self.assertEqual(response.status_code, 200) + # Check if the expected message tag is set + if test_name == "RegistryError" or test_name == "ContactError": + message_tag = "error" + elif test_name == "RegistrySuccess": + message_tag = "success" + else: + # Handle other cases if needed + message_tag = "info" # Change to the appropriate default + # Check the message tag + messages = list(response.context["messages"]) + self.assertEqual(len(messages), 1) + message = messages[0] + self.assertEqual(message.tags, message_tag) + self.assertEqual(message.message.strip(), expected_message.strip()) + + def test_domain_overview_blocked_for_ineligible_user(self): + """We could easily duplicate this test for all domain management + views, but a single url test should be solid enough since all domain + management pages share the same permissions class""" + self.user.status = User.RESTRICTED + self.user.save() + home_page = self.app.get("/") + self.assertContains(home_page, "igorville.gov") + with less_console_noise(): + response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) + self.assertEqual(response.status_code, 403) + + +class TestDomainDNSSEC(TestDomainOverview): + + """MockEPPLib is already inherited.""" + + def test_dnssec_page_refreshes_enable_button(self): + """DNSSEC overview page loads when domain has no DNSSEC data + and shows a 'Enable DNSSEC' button.""" + + page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id})) + self.assertContains(page, "Enable DNSSEC") + + def test_dnssec_page_loads_with_data_in_domain(self): + """DNSSEC overview page loads when domain has DNSSEC data + and the template contains a button to disable DNSSEC.""" + + page = self.client.get(reverse("domain-dns-dnssec", kwargs={"pk": self.domain_multdsdata.id})) + self.assertContains(page, "Disable DNSSEC") + + # Prepare the data for the POST request + post_data = { + "disable_dnssec": "Disable DNSSEC", + } + updated_page = self.client.post( + reverse("domain-dns-dnssec", kwargs={"pk": self.domain.id}), + post_data, + follow=True, + ) + + self.assertEqual(updated_page.status_code, 200) + + self.assertContains(updated_page, "Enable DNSSEC") + + def test_ds_form_loads_with_no_domain_data(self): + """DNSSEC Add DS data page loads when there is no + domain DNSSEC data and shows a button to Add new record""" + + page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id})) + self.assertContains(page, "You have no DS data added") + self.assertContains(page, "Add new record") + + def test_ds_form_loads_with_ds_data(self): + """DNSSEC Add DS data page loads when there is + domain DNSSEC DS data and shows the data""" + + page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) + self.assertContains(page, "DS data record 1") + + def test_ds_data_form_modal(self): + """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})) + # Assert that a hidden trigger for the modal does not exist. + # This hidden trigger will pop on the page when certain condition are met: + # 1) Initial form contained DS data, 2) All data is deleted and form is + # submitted. + self.assertNotContains(add_data_page, "Trigger Disable DNSSEC Modal") + # Simulate a delete all data + form_data = {} + response = self.client.post( + reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}), + data=form_data, + ) + self.assertEqual(response.status_code, 200) # Adjust status code as needed + # Now check to see whether the JS trigger for the modal is present on the page + self.assertContains(response, "Trigger Disable DNSSEC Modal") + + def test_ds_data_form_submits(self): + """DS data form submits successfully + + Uses self.app WebTest because we need to interact with forms. + """ + 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] + 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() + # form submission was a post, response should be a redirect + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id}), + ) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + page = result.follow() + self.assertContains(page, "The DS data records for this domain have been updated.") + + def test_ds_data_form_invalid(self): + """DS data form errors with invalid data (missing required fields) + + Uses self.app WebTest because we need to interact with forms. + """ + 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] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # all four form fields are required, so will test with each blank + add_data_page.forms[0]["form-0-key_tag"] = "" + 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"] = "" + with less_console_noise(): # swallow logged warning message + result = add_data_page.forms[0].submit() + # 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 + # the field. + self.assertContains(result, "Key tag is required", count=2, status_code=200) + self.assertContains(result, "Algorithm 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) + + def test_ds_data_form_invalid_keytag(self): + """DS data form errors with invalid data (key tag too large) + + Uses self.app WebTest because we need to interact with forms. + """ + 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] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # first two nameservers are required, so if we empty one out we should + # get a form error + add_data_page.forms[0]["form-0-key_tag"] = "65536" # > 65535 + 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"] = "" + with less_console_noise(): # swallow logged warning message + result = add_data_page.forms[0].submit() + # 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 + # the field. + self.assertContains( + result, str(DsDataError(code=DsDataErrorCodes.INVALID_KEYTAG_SIZE)), count=2, status_code=200 + ) + + def test_ds_data_form_invalid_digest_chars(self): + """DS data form errors with invalid data (digest contains non hexadecimal chars) + + Uses self.app WebTest because we need to interact with forms. + """ + 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] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # first two nameservers are required, so if we empty one out we should + # get a form error + add_data_page.forms[0]["form-0-key_tag"] = "1234" + 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"] = "GG1234" + with less_console_noise(): # swallow logged warning message + result = add_data_page.forms[0].submit() + # 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 + # the field. + self.assertContains( + result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_CHARS)), count=2, status_code=200 + ) + + def test_ds_data_form_invalid_digest_sha1(self): + """DS data form errors with invalid data (digest is invalid sha-1) + + Uses self.app WebTest because we need to interact with forms. + """ + 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] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # first two nameservers are required, so if we empty one out we should + # get a form error + add_data_page.forms[0]["form-0-key_tag"] = "1234" + 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"] = "A123" + with less_console_noise(): # swallow logged warning message + result = add_data_page.forms[0].submit() + # 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 + # the field. + self.assertContains( + result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA1)), count=2, status_code=200 + ) + + def test_ds_data_form_invalid_digest_sha256(self): + """DS data form errors with invalid data (digest is invalid sha-256) + + Uses self.app WebTest because we need to interact with forms. + """ + 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] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + # first two nameservers are required, so if we empty one out we should + # get a form error + add_data_page.forms[0]["form-0-key_tag"] = "1234" + 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"] = "GG1234" + with less_console_noise(): # swallow logged warning message + result = add_data_page.forms[0].submit() + # 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 + # the field. + self.assertContains( + result, str(DsDataError(code=DsDataErrorCodes.INVALID_DIGEST_SHA256)), count=2, status_code=200 + ) diff --git a/src/registrar/views/__init__.py b/src/registrar/views/__init__.py index c1400d7c0..8785c9076 100644 --- a/src/registrar/views/__init__.py +++ b/src/registrar/views/__init__.py @@ -12,6 +12,7 @@ from .domain import ( DomainUsersView, DomainAddUserView, DomainInvitationDeleteView, + DomainDeleteUserView, ) from .health import * from .index import * diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 4dce2301c..04fe1ce3a 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -34,6 +34,7 @@ from registrar.utility.errors import ( SecurityEmailErrorCodes, ) from registrar.models.utility.contact_error import ContactError +from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView from ..forms import ( ContactForm, @@ -632,6 +633,55 @@ class DomainUsersView(DomainBaseView): template_name = "domain_users.html" + def get_context_data(self, **kwargs): + """The initial value for the form (which is a formset here).""" + context = super().get_context_data(**kwargs) + + # Add conditionals to the context (such as "can_delete_users") + context = self._add_booleans_to_context(context) + + # Add modal buttons to the context (such as for delete) + context = self._add_modal_buttons_to_context(context) + + # Get the email of the current user + context["current_user_email"] = self.request.user.email + + return context + + def _add_booleans_to_context(self, context): + # Determine if the current user can delete managers + domain_pk = None + can_delete_users = False + + if self.kwargs is not None and "pk" in self.kwargs: + domain_pk = self.kwargs["pk"] + # Prevent the end user from deleting themselves as a manager if they are the + # only manager that exists on a domain. + can_delete_users = UserDomainRole.objects.filter(domain__id=domain_pk).count() > 1 + + context["can_delete_users"] = can_delete_users + return context + + def _add_modal_buttons_to_context(self, context): + """Adds modal buttons (and their HTML) to the context""" + # Create HTML for the modal button + modal_button = ( + '' + ) + context["modal_button"] = modal_button + + # Create HTML for the modal button when deleting yourself + modal_button_self = ( + '' + ) + context["modal_button_self"] = modal_button_self + + return context + class DomainAddUserView(DomainFormBaseView): """Inside of a domain's user management, a form for adding users. @@ -745,3 +795,60 @@ class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMe def get_success_message(self, cleaned_data): return f"Successfully canceled invitation for {self.object.email}." + + +class DomainDeleteUserView(UserDomainRolePermissionDeleteView): + """Inside of a domain's user management, a form for deleting users.""" + + object: UserDomainRole # workaround for type mismatch in DeleteView + + def get_object(self, queryset=None): + """Custom get_object definition to grab a UserDomainRole object from a domain_id and user_id""" + domain_id = self.kwargs.get("pk") + user_id = self.kwargs.get("user_pk") + return UserDomainRole.objects.get(domain=domain_id, user=user_id) + + def get_success_url(self): + """Refreshes the page after a delete is successful""" + return reverse("domain-users", kwargs={"pk": self.object.domain.id}) + + def get_success_message(self, delete_self=False): + """Returns confirmation content for the deletion event""" + + # Grab the text representation of the user we want to delete + email_or_name = self.object.user.email + if email_or_name is None or email_or_name.strip() == "": + email_or_name = self.object.user + + # If the user is deleting themselves, return a specific message. + # If not, return something more generic. + if delete_self: + message = f"You are no longer managing the domain {self.object.domain}." + else: + message = f"Removed {email_or_name} as a manager for this domain." + + return message + + def form_valid(self, form): + """Delete the specified user on this domain.""" + + # Delete the object + super().form_valid(form) + + # Is the user deleting themselves? If so, display a different message + delete_self = self.request.user == self.object.user + + # Add a success message + messages.success(self.request, self.get_success_message(delete_self)) + return redirect(self.get_success_url()) + + def post(self, request, *args, **kwargs): + """Custom post implementation to redirect to home in the event that the user deletes themselves""" + response = super().post(request, *args, **kwargs) + + # If the user is deleting themselves, redirect to home + delete_self = self.request.user == self.object.user + if delete_self: + return redirect(reverse("home")) + + return response diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index 0cf5970df..b2c4cb364 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -286,6 +286,43 @@ class DomainApplicationPermission(PermissionsLoginMixin): return True +class UserDeleteDomainRolePermission(PermissionsLoginMixin): + + """Permission mixin for UserDomainRole if user + has access, otherwise 403""" + + def has_permission(self): + """Check if this user has access to this domain application. + + The user is in self.request.user and the domain needs to be looked + up from the domain's primary key in self.kwargs["pk"] + """ + domain_pk = self.kwargs["pk"] + user_pk = self.kwargs["user_pk"] + + # Check if the user is authenticated + if not self.request.user.is_authenticated: + return False + + # Check if the UserDomainRole object exists, then check + # if the user requesting the delete has permissions to do so + has_delete_permission = UserDomainRole.objects.filter( + user=user_pk, + domain=domain_pk, + domain__permissions__user=self.request.user, + ).exists() + if not has_delete_permission: + return False + + # Check if more than one manager exists on the domain. + # If only one exists, prevent this from happening + has_multiple_managers = len(UserDomainRole.objects.filter(domain=domain_pk)) > 1 + if not has_multiple_managers: + return False + + return True + + class DomainApplicationPermissionWithdraw(PermissionsLoginMixin): """Permission mixin that redirects to withdraw action on domain application diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 587eb0b5c..54c96d602 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -4,6 +4,7 @@ import abc # abstract base class from django.views.generic import DetailView, DeleteView, TemplateView from registrar.models import Domain, DomainApplication, DomainInvitation +from registrar.models.user_domain_role import UserDomainRole from .mixins import ( DomainPermission, @@ -11,6 +12,7 @@ from .mixins import ( DomainApplicationPermissionWithdraw, DomainInvitationPermission, ApplicationWizardPermission, + UserDeleteDomainRolePermission, ) import logging @@ -130,3 +132,20 @@ class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteV model = DomainApplication object: DomainApplication + + +class UserDomainRolePermissionDeleteView(UserDeleteDomainRolePermission, DeleteView, abc.ABC): + + """Abstract base view for deleting a UserDomainRole. + + This abstract view cannot be instantiated. Actual views must specify + `template_name`. + """ + + # DetailView property for what model this is viewing + model = UserDomainRole + # workaround for type mismatch in DeleteView + object: UserDomainRole + + # variable name in template context for the model object + context_object_name = "userdomainrole"