mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-17 10:07:04 +02:00
Merge branch 'main' into za/1602-extend-expirations-easily
This commit is contained in:
commit
ca43d91641
68 changed files with 5306 additions and 4589 deletions
8
.github/workflows/deploy-stable.yaml
vendored
8
.github/workflows/deploy-stable.yaml
vendored
|
@ -37,3 +37,11 @@ jobs:
|
||||||
cf_org: cisa-dotgov
|
cf_org: cisa-dotgov
|
||||||
cf_space: stable
|
cf_space: stable
|
||||||
cf_manifest: "ops/manifests/manifest-stable.yaml"
|
cf_manifest: "ops/manifests/manifest-stable.yaml"
|
||||||
|
- name: Run Django migrations
|
||||||
|
uses: cloud-gov/cg-cli-tools@main
|
||||||
|
with:
|
||||||
|
cf_username: ${{ secrets.CF_STABLE_USERNAME }}
|
||||||
|
cf_password: ${{ secrets.CF_STABLE_PASSWORD }}
|
||||||
|
cf_org: cisa-dotgov
|
||||||
|
cf_space: stable
|
||||||
|
cf_command: "run-task getgov-stable --command 'python manage.py migrate' --name migrate"
|
8
.github/workflows/deploy-staging.yaml
vendored
8
.github/workflows/deploy-staging.yaml
vendored
|
@ -37,3 +37,11 @@ jobs:
|
||||||
cf_org: cisa-dotgov
|
cf_org: cisa-dotgov
|
||||||
cf_space: staging
|
cf_space: staging
|
||||||
cf_manifest: "ops/manifests/manifest-staging.yaml"
|
cf_manifest: "ops/manifests/manifest-staging.yaml"
|
||||||
|
- name: Run Django migrations
|
||||||
|
uses: cloud-gov/cg-cli-tools@main
|
||||||
|
with:
|
||||||
|
cf_username: ${{ secrets.CF_STAGING_USERNAME }}
|
||||||
|
cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
|
||||||
|
cf_org: cisa-dotgov
|
||||||
|
cf_space: staging
|
||||||
|
cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate"
|
||||||
|
|
|
@ -4,7 +4,7 @@ verify_ssl = true
|
||||||
name = "pypi"
|
name = "pypi"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
django = "*"
|
django = "4.2.10"
|
||||||
cfenv = "*"
|
cfenv = "*"
|
||||||
django-cors-headers = "*"
|
django-cors-headers = "*"
|
||||||
pycryptodomex = "*"
|
pycryptodomex = "*"
|
||||||
|
|
1137
src/Pipfile.lock
generated
1137
src/Pipfile.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -19,7 +19,6 @@ API_BASE_PATH = "/api/v1/available/?domain="
|
||||||
|
|
||||||
|
|
||||||
class AvailableViewTest(MockEppLib):
|
class AvailableViewTest(MockEppLib):
|
||||||
|
|
||||||
"""Test that the view function works as expected."""
|
"""Test that the view function works as expected."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -123,7 +122,6 @@ class AvailableViewTest(MockEppLib):
|
||||||
|
|
||||||
|
|
||||||
class AvailableAPITest(MockEppLib):
|
class AvailableAPITest(MockEppLib):
|
||||||
|
|
||||||
"""Test that the API can be called as expected."""
|
"""Test that the API can be called as expected."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Internal API views"""
|
"""Internal API views"""
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
|
@ -4,13 +4,13 @@ from django.http import HttpResponse
|
||||||
from django.test import Client, TestCase, RequestFactory
|
from django.test import Client, TestCase, RequestFactory
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from djangooidc.exceptions import NoStateDefined
|
from djangooidc.exceptions import NoStateDefined, InternalError
|
||||||
from ..views import login_callback
|
from ..views import login_callback
|
||||||
|
|
||||||
from .common import less_console_noise
|
from .common import less_console_noise
|
||||||
|
|
||||||
|
|
||||||
@patch("djangooidc.views.CLIENT", autospec=True)
|
@patch("djangooidc.views.CLIENT", new_callable=MagicMock)
|
||||||
class ViewsTest(TestCase):
|
class ViewsTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
@ -35,116 +35,253 @@ class ViewsTest(TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_openid_sets_next(self, mock_client):
|
def test_openid_sets_next(self, mock_client):
|
||||||
# setup
|
"""Test that the openid method properly sets next in the session."""
|
||||||
|
with less_console_noise():
|
||||||
|
# SETUP
|
||||||
|
# set up the callback url that will be tested in assertions against
|
||||||
|
# session[next]
|
||||||
callback_url = reverse("openid_login_callback")
|
callback_url = reverse("openid_login_callback")
|
||||||
# mock
|
# MOCK
|
||||||
|
# when login is called, response from create_authn_request should
|
||||||
|
# be returned to user, so let's mock it and test it
|
||||||
mock_client.create_authn_request.side_effect = self.say_hi
|
mock_client.create_authn_request.side_effect = self.say_hi
|
||||||
|
# in this case, we need to mock the get_default_acr_value so that
|
||||||
|
# openid method will execute properly, but the acr_value itself
|
||||||
|
# is not important for this test
|
||||||
mock_client.get_default_acr_value.side_effect = self.create_acr
|
mock_client.get_default_acr_value.side_effect = self.create_acr
|
||||||
# test
|
# TEST
|
||||||
|
# test the login url, passing a callback url
|
||||||
response = self.client.get(reverse("login"), {"next": callback_url})
|
response = self.client.get(reverse("login"), {"next": callback_url})
|
||||||
# assert
|
# ASSERTIONS
|
||||||
session = mock_client.create_authn_request.call_args[0][0]
|
session = mock_client.create_authn_request.call_args[0][0]
|
||||||
|
# assert the session[next] is set to the callback_url
|
||||||
self.assertEqual(session["next"], callback_url)
|
self.assertEqual(session["next"], callback_url)
|
||||||
|
# assert that openid returned properly the response from
|
||||||
|
# create_authn_request
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, "Hi")
|
self.assertContains(response, "Hi")
|
||||||
|
|
||||||
def test_openid_raises(self, mock_client):
|
def test_openid_raises(self, mock_client):
|
||||||
# mock
|
"""Test that errors in openid raise 500 error for the user.
|
||||||
mock_client.create_authn_request.side_effect = Exception("Test")
|
This test specifically tests for any exceptions that might be raised from
|
||||||
# test
|
create_authn_request. This includes scenarios where CLIENT exists, but
|
||||||
|
is no longer functioning properly."""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
|
# when login is called, exception thrown from create_authn_request
|
||||||
|
# should present 500 error page to user
|
||||||
|
mock_client.create_authn_request.side_effect = Exception("Test")
|
||||||
|
# TEST
|
||||||
|
# test when login url is called
|
||||||
response = self.client.get(reverse("login"))
|
response = self.client.get(reverse("login"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
|
# assert that the 500 error page is raised
|
||||||
self.assertEqual(response.status_code, 500)
|
self.assertEqual(response.status_code, 500)
|
||||||
self.assertTemplateUsed(response, "500.html")
|
self.assertTemplateUsed(response, "500.html")
|
||||||
self.assertIn("Server error", response.content.decode("utf-8"))
|
self.assertIn("Server error", response.content.decode("utf-8"))
|
||||||
|
|
||||||
def test_callback_with_no_session_state(self, mock_client):
|
def test_openid_raises_when_client_is_none_and_cant_init(self, mock_client):
|
||||||
|
"""Test that errors in openid raise 500 error for the user.
|
||||||
|
This test specifically tests for the condition where the CLIENT
|
||||||
|
is None and the client initialization attempt raises an exception."""
|
||||||
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
|
# mock that CLIENT is None
|
||||||
|
# mock that Client() raises an exception (by mocking _initialize_client)
|
||||||
|
# Patch CLIENT to None for this specific test
|
||||||
|
with patch("djangooidc.views.CLIENT", None):
|
||||||
|
# Patch _initialize_client() to raise an exception
|
||||||
|
with patch("djangooidc.views._initialize_client") as mock_init:
|
||||||
|
mock_init.side_effect = InternalError
|
||||||
|
# TEST
|
||||||
|
# test when login url is called
|
||||||
|
response = self.client.get(reverse("login"))
|
||||||
|
# ASSERTIONS
|
||||||
|
# assert that the 500 error page is raised
|
||||||
|
self.assertEqual(response.status_code, 500)
|
||||||
|
self.assertTemplateUsed(response, "500.html")
|
||||||
|
self.assertIn("Server error", response.content.decode("utf-8"))
|
||||||
|
|
||||||
|
def test_openid_initializes_client_and_calls_create_authn_request(self, mock_client):
|
||||||
|
"""Test that openid re-initializes the client when the client had not
|
||||||
|
been previously initiated."""
|
||||||
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
|
# response from create_authn_request should
|
||||||
|
# be returned to user, so let's mock it and test it
|
||||||
|
mock_client.create_authn_request.side_effect = self.say_hi
|
||||||
|
# in this case, we need to mock the get_default_acr_value so that
|
||||||
|
# openid method will execute properly, but the acr_value itself
|
||||||
|
# is not important for this test
|
||||||
|
mock_client.get_default_acr_value.side_effect = self.create_acr
|
||||||
|
with patch("djangooidc.views._initialize_client") as mock_init_client:
|
||||||
|
with patch("djangooidc.views._client_is_none") as mock_client_is_none:
|
||||||
|
# mock the client to initially be None
|
||||||
|
mock_client_is_none.return_value = True
|
||||||
|
# TEST
|
||||||
|
# test when login url is called
|
||||||
|
response = self.client.get(reverse("login"))
|
||||||
|
# ASSERTIONS
|
||||||
|
# assert that _initialize_client was called
|
||||||
|
mock_init_client.assert_called_once()
|
||||||
|
# assert that the response is the mocked response from create_authn_request
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "Hi")
|
||||||
|
|
||||||
|
def test_login_callback_with_no_session_state(self, mock_client):
|
||||||
"""If the local session is None (ie the server restarted while user was logged out),
|
"""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."""
|
we do not throw an exception. Rather, we attempt to login again."""
|
||||||
# mock
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
|
# mock the acr_value to some string
|
||||||
|
# mock the callback function to raise the NoStateDefined Exception
|
||||||
mock_client.get_default_acr_value.side_effect = self.create_acr
|
mock_client.get_default_acr_value.side_effect = self.create_acr
|
||||||
mock_client.callback.side_effect = NoStateDefined()
|
mock_client.callback.side_effect = NoStateDefined()
|
||||||
# test
|
# TEST
|
||||||
with less_console_noise():
|
# test the login callback
|
||||||
response = self.client.get(reverse("openid_login_callback"))
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
|
# assert that the user is redirected to the start of the login process
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, "/")
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
def test_login_callback_reads_next(self, mock_client):
|
def test_login_callback_reads_next(self, mock_client):
|
||||||
# setup
|
"""If the next value is set in the session, test that login_callback returns
|
||||||
|
a redirect to the 'next' url."""
|
||||||
|
with less_console_noise():
|
||||||
|
# SETUP
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
|
# set 'next' to the logout url
|
||||||
session["next"] = reverse("logout")
|
session["next"] = reverse("logout")
|
||||||
session.save()
|
session.save()
|
||||||
# mock
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
# test
|
# patch that the request does not require step up auth
|
||||||
with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise():
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False):
|
||||||
response = self.client.get(reverse("openid_login_callback"))
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
|
# assert the redirect url is the same as the 'next' value set in session
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse("logout"))
|
self.assertEqual(response.url, reverse("logout"))
|
||||||
|
|
||||||
def test_login_callback_no_step_up_auth(self, mock_client):
|
def test_login_callback_raises_when_client_is_none_and_cant_init(self, mock_client):
|
||||||
"""Walk through login_callback when requires_step_up_auth returns False
|
"""Test that errors in login_callback raise 500 error for the user.
|
||||||
and assert that we have a redirect to /"""
|
This test specifically tests for the condition where the CLIENT
|
||||||
# setup
|
is None and the client initialization attempt raises an exception."""
|
||||||
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
|
# mock that CLIENT is None
|
||||||
|
# mock that Client() raises an exception (by mocking _initialize_client)
|
||||||
|
# Patch CLIENT to None for this specific test
|
||||||
|
with patch("djangooidc.views.CLIENT", None):
|
||||||
|
# Patch _initialize_client() to raise an exception
|
||||||
|
with patch("djangooidc.views._initialize_client") as mock_init:
|
||||||
|
mock_init.side_effect = InternalError
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
# ASSERTIONS
|
||||||
|
# assert that the 500 error page is raised
|
||||||
|
self.assertEqual(response.status_code, 500)
|
||||||
|
self.assertTemplateUsed(response, "500.html")
|
||||||
|
self.assertIn("Server error", response.content.decode("utf-8"))
|
||||||
|
|
||||||
|
def test_login_callback_initializes_client_and_succeeds(self, mock_client):
|
||||||
|
"""Test that openid re-initializes the client when the client had not
|
||||||
|
been previously initiated."""
|
||||||
|
with less_console_noise():
|
||||||
|
# SETUP
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
session.save()
|
session.save()
|
||||||
# mock
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
# test
|
# patch that the request does not require step up auth
|
||||||
with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise():
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False):
|
||||||
|
with patch("djangooidc.views._initialize_client") as mock_init_client:
|
||||||
|
with patch("djangooidc.views._client_is_none") as mock_client_is_none:
|
||||||
|
# mock the client to initially be None
|
||||||
|
mock_client_is_none.return_value = True
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
response = self.client.get(reverse("openid_login_callback"))
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
|
# assert that _initialize_client was called
|
||||||
|
mock_init_client.assert_called_once()
|
||||||
|
# assert that redirect is to / when no 'next' is set
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, "/")
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
def test_requires_step_up_auth(self, mock_client):
|
def test_login_callback_no_step_up_auth(self, mock_client):
|
||||||
"""Invoke login_callback passing it a request when requires_step_up_auth returns True
|
"""Walk through login_callback when _requires_step_up_auth returns False
|
||||||
|
and assert that we have a redirect to /"""
|
||||||
|
with less_console_noise():
|
||||||
|
# SETUP
|
||||||
|
session = self.client.session
|
||||||
|
session.save()
|
||||||
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
|
mock_client.callback.side_effect = self.user_info
|
||||||
|
# patch that the request does not require step up auth
|
||||||
|
# TEST
|
||||||
|
# test the login callback url
|
||||||
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False):
|
||||||
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
|
# ASSERTIONS
|
||||||
|
# assert that redirect is to / when no 'next' is set
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, "/")
|
||||||
|
|
||||||
|
def test_login_callback_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."""
|
and assert that session is updated and create_authn_request (mock) is called."""
|
||||||
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
# Configure the mock to return an expected value for get_step_up_acr_value
|
# 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"
|
mock_client.return_value.get_step_up_acr_value.return_value = "step_up_acr_value"
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.get("/some-url")
|
request = self.factory.get("/some-url")
|
||||||
request.session = {"acr_value": ""}
|
request.session = {"acr_value": ""}
|
||||||
|
|
||||||
# Ensure that the CLIENT instance used in login_callback is the mock
|
# Ensure that the CLIENT instance used in login_callback is the mock
|
||||||
# patch requires_step_up_auth to return True
|
# patch _requires_step_up_auth to return True
|
||||||
with patch("djangooidc.views.requires_step_up_auth", return_value=True), patch(
|
with patch("djangooidc.views._requires_step_up_auth", return_value=True), patch(
|
||||||
"djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock()
|
"djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock()
|
||||||
) as mock_create_authn_request:
|
) as mock_create_authn_request:
|
||||||
|
# TEST
|
||||||
|
# test the login callback
|
||||||
login_callback(request)
|
login_callback(request)
|
||||||
|
# ASSERTIONS
|
||||||
# create_authn_request only gets called when requires_step_up_auth is True
|
# create_authn_request only gets called when _requires_step_up_auth is True
|
||||||
# and it changes this acr_value in request.session
|
# and it changes this acr_value in request.session
|
||||||
|
|
||||||
# Assert that acr_value is no longer empty string
|
# Assert that acr_value is no longer empty string
|
||||||
self.assertNotEqual(request.session["acr_value"], "")
|
self.assertNotEqual(request.session["acr_value"], "")
|
||||||
# And create_authn_request was called again
|
# And create_authn_request was called again
|
||||||
mock_create_authn_request.assert_called_once()
|
mock_create_authn_request.assert_called_once()
|
||||||
|
|
||||||
def test_does_not_requires_step_up_auth(self, mock_client):
|
def test_login_callback_does_not_requires_step_up_auth(self, mock_client):
|
||||||
"""Invoke login_callback passing it a request when requires_step_up_auth returns False
|
"""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.
|
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"""
|
Possibly redundant with test_login_callback_requires_step_up_auth"""
|
||||||
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.get("/some-url")
|
request = self.factory.get("/some-url")
|
||||||
request.session = {"acr_value": ""}
|
request.session = {"acr_value": ""}
|
||||||
|
|
||||||
# Ensure that the CLIENT instance used in login_callback is the mock
|
# Ensure that the CLIENT instance used in login_callback is the mock
|
||||||
# patch requires_step_up_auth to return False
|
# patch _requires_step_up_auth to return False
|
||||||
with patch("djangooidc.views.requires_step_up_auth", return_value=False), patch(
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False), patch(
|
||||||
"djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock()
|
"djangooidc.views.CLIENT.create_authn_request", return_value=MagicMock()
|
||||||
) as mock_create_authn_request:
|
) as mock_create_authn_request:
|
||||||
|
# TEST
|
||||||
|
# test the login callback
|
||||||
login_callback(request)
|
login_callback(request)
|
||||||
|
# ASSERTIONS
|
||||||
# create_authn_request only gets called when requires_step_up_auth is True
|
# create_authn_request only gets called when _requires_step_up_auth is True
|
||||||
# and it changes this acr_value in request.session
|
# 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
|
# Assert that acr_value is NOT updated by testing that it is still an empty string
|
||||||
self.assertEqual(request.session["acr_value"], "")
|
self.assertEqual(request.session["acr_value"], "")
|
||||||
# Assert create_authn_request was not called
|
# Assert create_authn_request was not called
|
||||||
|
@ -152,31 +289,36 @@ class ViewsTest(TestCase):
|
||||||
|
|
||||||
@patch("djangooidc.views.authenticate")
|
@patch("djangooidc.views.authenticate")
|
||||||
def test_login_callback_raises(self, mock_auth, mock_client):
|
def test_login_callback_raises(self, mock_auth, mock_client):
|
||||||
# mock
|
"""Test that login callback raises a 401 when user is unauthorized"""
|
||||||
|
with less_console_noise():
|
||||||
|
# MOCK
|
||||||
|
# mock that callback returns user_info; this is the expected behavior
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
mock_auth.return_value = None
|
mock_auth.return_value = None
|
||||||
# test
|
# TEST
|
||||||
with patch("djangooidc.views.requires_step_up_auth", return_value=False), less_console_noise():
|
with patch("djangooidc.views._requires_step_up_auth", return_value=False):
|
||||||
response = self.client.get(reverse("openid_login_callback"))
|
response = self.client.get(reverse("openid_login_callback"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
self.assertTemplateUsed(response, "401.html")
|
self.assertTemplateUsed(response, "401.html")
|
||||||
self.assertIn("Unauthorized", response.content.decode("utf-8"))
|
self.assertIn("Unauthorized", response.content.decode("utf-8"))
|
||||||
|
|
||||||
def test_logout_redirect_url(self, mock_client):
|
def test_logout_redirect_url(self, mock_client):
|
||||||
# setup
|
"""Test that logout redirects to the configured post_logout_redirect_uris."""
|
||||||
|
with less_console_noise():
|
||||||
|
# SETUP
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
session["state"] = "TEST" # nosec B105
|
session["state"] = "TEST" # nosec B105
|
||||||
session.save()
|
session.save()
|
||||||
# mock
|
# MOCK
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
mock_client.registration_response = {"post_logout_redirect_uris": ["http://example.com/back"]}
|
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.provider_info = {"end_session_endpoint": "http://example.com/log_me_out"}
|
||||||
mock_client.client_id = "TEST"
|
mock_client.client_id = "TEST"
|
||||||
# test
|
# TEST
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
response = self.client.get(reverse("logout"))
|
response = self.client.get(reverse("logout"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
expected = (
|
expected = (
|
||||||
"http://example.com/log_me_out?client_id=TEST&state"
|
"http://example.com/log_me_out?client_id=TEST&state"
|
||||||
"=TEST&post_logout_redirect_uri=http%3A%2F%2Fexample.com%2Fback"
|
"=TEST&post_logout_redirect_uri=http%3A%2F%2Fexample.com%2Fback"
|
||||||
|
@ -185,21 +327,46 @@ class ViewsTest(TestCase):
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_logout_redirect_url_with_no_session_state(self, mock_client):
|
||||||
|
"""Test that logout redirects to the configured post_logout_redirect_uris."""
|
||||||
|
with less_console_noise():
|
||||||
|
# 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"))
|
||||||
|
# ASSERTIONS
|
||||||
|
# Assert redirect code and url are accurate
|
||||||
|
expected = (
|
||||||
|
"http://example.com/log_me_out?client_id=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")
|
@patch("djangooidc.views.auth_logout")
|
||||||
def test_logout_always_logs_out(self, mock_logout, _):
|
def test_logout_always_logs_out(self, mock_logout, _):
|
||||||
# Without additional mocking, logout will always fail.
|
"""Without additional mocking, logout will always fail.
|
||||||
# Here we test that auth_logout is called regardless
|
Here we test that auth_logout is called regardless"""
|
||||||
|
# TEST
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.client.get(reverse("logout"))
|
self.client.get(reverse("logout"))
|
||||||
|
# ASSERTIONS
|
||||||
self.assertTrue(mock_logout.called)
|
self.assertTrue(mock_logout.called)
|
||||||
|
|
||||||
def test_logout_callback_redirects(self, _):
|
def test_logout_callback_redirects(self, _):
|
||||||
# setup
|
"""Test that the logout_callback redirects properly"""
|
||||||
|
with less_console_noise():
|
||||||
|
# SETUP
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
session["next"] = reverse("logout")
|
session["next"] = reverse("logout")
|
||||||
session.save()
|
session.save()
|
||||||
# test
|
# TEST
|
||||||
response = self.client.get(reverse("openid_logout_callback"))
|
response = self.client.get(reverse("openid_logout_callback"))
|
||||||
# assert
|
# ASSERTIONS
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse("logout"))
|
self.assertEqual(response.url, reverse("logout"))
|
||||||
|
|
|
@ -15,15 +15,34 @@ from registrar.models import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
CLIENT = None
|
||||||
|
|
||||||
|
|
||||||
|
def _initialize_client():
|
||||||
|
"""Initialize the OIDC client. Exceptions are allowed to raise
|
||||||
|
and will need to be caught."""
|
||||||
|
global CLIENT
|
||||||
# Initialize provider using pyOICD
|
# Initialize provider using pyOICD
|
||||||
OP = getattr(settings, "OIDC_ACTIVE_PROVIDER")
|
OP = getattr(settings, "OIDC_ACTIVE_PROVIDER")
|
||||||
CLIENT = Client(OP)
|
CLIENT = Client(OP)
|
||||||
logger.debug("client initialized %s" % CLIENT)
|
logger.debug("Client initialized: %s" % CLIENT)
|
||||||
|
|
||||||
|
|
||||||
|
def _client_is_none():
|
||||||
|
"""Return if the CLIENT is currently None."""
|
||||||
|
global CLIENT
|
||||||
|
return CLIENT is None
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize CLIENT
|
||||||
|
try:
|
||||||
|
_initialize_client()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
CLIENT = None # type: ignore
|
# In the event of an exception, log the error and allow the app load to continue
|
||||||
logger.warning(err)
|
# without the OIDC Client. Subsequent login attempts will attempt to initialize
|
||||||
logger.warning("Unable to configure OpenID Connect provider. Users cannot log in.")
|
# again if Client is None
|
||||||
|
logger.error(err)
|
||||||
|
logger.error("Unable to configure OpenID Connect provider. Users cannot log in.")
|
||||||
|
|
||||||
|
|
||||||
def error_page(request, error):
|
def error_page(request, error):
|
||||||
|
@ -55,13 +74,15 @@ def error_page(request, error):
|
||||||
|
|
||||||
def openid(request):
|
def openid(request):
|
||||||
"""Redirect the user to an authentication provider (OP)."""
|
"""Redirect the user to an authentication provider (OP)."""
|
||||||
|
global CLIENT
|
||||||
# If the session reset because of a server restart, attempt to login again
|
|
||||||
request.session["acr_value"] = CLIENT.get_default_acr_value()
|
|
||||||
|
|
||||||
request.session["next"] = request.GET.get("next", "/")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# If the CLIENT is none, attempt to reinitialize before handling the request
|
||||||
|
if _client_is_none():
|
||||||
|
logger.debug("OIDC client is None, attempting to initialize")
|
||||||
|
_initialize_client()
|
||||||
|
request.session["acr_value"] = CLIENT.get_default_acr_value()
|
||||||
|
request.session["next"] = request.GET.get("next", "/")
|
||||||
|
# Create the authentication request
|
||||||
return CLIENT.create_authn_request(request.session)
|
return CLIENT.create_authn_request(request.session)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return error_page(request, err)
|
return error_page(request, err)
|
||||||
|
@ -69,12 +90,17 @@ def openid(request):
|
||||||
|
|
||||||
def login_callback(request):
|
def login_callback(request):
|
||||||
"""Analyze the token returned by the authentication provider (OP)."""
|
"""Analyze the token returned by the authentication provider (OP)."""
|
||||||
|
global CLIENT
|
||||||
try:
|
try:
|
||||||
|
# If the CLIENT is none, attempt to reinitialize before handling the request
|
||||||
|
if _client_is_none():
|
||||||
|
logger.debug("OIDC client is None, attempting to initialize")
|
||||||
|
_initialize_client()
|
||||||
query = parse_qs(request.GET.urlencode())
|
query = parse_qs(request.GET.urlencode())
|
||||||
userinfo = CLIENT.callback(query, request.session)
|
userinfo = CLIENT.callback(query, request.session)
|
||||||
# test for need for identity verification and if it is satisfied
|
# test for need for identity verification and if it is satisfied
|
||||||
# if not satisfied, redirect user to login with stepped up acr_value
|
# if not satisfied, redirect user to login with stepped up acr_value
|
||||||
if requires_step_up_auth(userinfo):
|
if _requires_step_up_auth(userinfo):
|
||||||
# add acr_value to request.session
|
# add acr_value to request.session
|
||||||
request.session["acr_value"] = CLIENT.get_step_up_acr_value()
|
request.session["acr_value"] = CLIENT.get_step_up_acr_value()
|
||||||
return CLIENT.create_authn_request(request.session)
|
return CLIENT.create_authn_request(request.session)
|
||||||
|
@ -87,13 +113,16 @@ def login_callback(request):
|
||||||
else:
|
else:
|
||||||
raise o_e.BannedUser()
|
raise o_e.BannedUser()
|
||||||
except o_e.NoStateDefined as nsd_err:
|
except o_e.NoStateDefined as nsd_err:
|
||||||
|
# In the event that a user is in the middle of a login when the app is restarted,
|
||||||
|
# their session state will no longer be available, so redirect the user to the
|
||||||
|
# beginning of login process without raising an error to the user.
|
||||||
logger.warning(f"No State Defined: {nsd_err}")
|
logger.warning(f"No State Defined: {nsd_err}")
|
||||||
return redirect(request.session.get("next", "/"))
|
return redirect(request.session.get("next", "/"))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return error_page(request, err)
|
return error_page(request, err)
|
||||||
|
|
||||||
|
|
||||||
def requires_step_up_auth(userinfo):
|
def _requires_step_up_auth(userinfo):
|
||||||
"""if User.needs_identity_verification and step_up_acr_value not in
|
"""if User.needs_identity_verification and step_up_acr_value not in
|
||||||
ial returned from callback, return True"""
|
ial returned from callback, return True"""
|
||||||
step_up_acr_value = CLIENT.get_step_up_acr_value()
|
step_up_acr_value = CLIENT.get_step_up_acr_value()
|
||||||
|
@ -116,8 +145,12 @@ def logout(request, next_page=None):
|
||||||
user = request.user
|
user = request.user
|
||||||
request_args = {
|
request_args = {
|
||||||
"client_id": CLIENT.client_id,
|
"client_id": CLIENT.client_id,
|
||||||
"state": request.session["state"],
|
|
||||||
}
|
}
|
||||||
|
# if state is not in request session, still redirect to the identity
|
||||||
|
# provider's logout url, but don't include the state in the url; this
|
||||||
|
# will successfully log out of the identity provider
|
||||||
|
if "state" in request.session:
|
||||||
|
request_args["state"] = request.session["state"]
|
||||||
if (
|
if (
|
||||||
"post_logout_redirect_uris" in CLIENT.registration_response.keys()
|
"post_logout_redirect_uris" in CLIENT.registration_response.keys()
|
||||||
and len(CLIENT.registration_response["post_logout_redirect_uris"]) > 0
|
and len(CLIENT.registration_response["post_logout_redirect_uris"]) > 0
|
||||||
|
|
51
src/epplibwrapper/tests/common.py
Normal file
51
src/epplibwrapper/tests/common.py
Normal file
|
@ -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()
|
|
@ -9,7 +9,7 @@ from epplibwrapper.socket import Socket
|
||||||
from epplibwrapper.utility.pool import EPPConnectionPool
|
from epplibwrapper.utility.pool import EPPConnectionPool
|
||||||
from registrar.models.domain import registry
|
from registrar.models.domain import registry
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
|
from .common import less_console_noise
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -135,6 +135,7 @@ class TestConnectionPool(TestCase):
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing))
|
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, "send", self.fake_send))
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
||||||
|
with less_console_noise():
|
||||||
# Restart the connection pool
|
# Restart the connection pool
|
||||||
registry.start_connection_pool()
|
registry.start_connection_pool()
|
||||||
# Pool should be running, and be the right size
|
# Pool should be running, and be the right size
|
||||||
|
@ -152,6 +153,8 @@ class TestConnectionPool(TestCase):
|
||||||
# The number of open pools should match the number of requested ones.
|
# The number of open pools should match the number of requested ones.
|
||||||
# If it is 0, then they failed to open
|
# If it is 0, then they failed to open
|
||||||
self.assertEqual(len(registry._pool.conn), self.pool_options["size"])
|
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)
|
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
||||||
def test_pool_restarts_on_send(self):
|
def test_pool_restarts_on_send(self):
|
||||||
|
@ -198,16 +201,22 @@ class TestConnectionPool(TestCase):
|
||||||
xml = (location).read_bytes()
|
xml = (location).read_bytes()
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
def do_nothing(command):
|
||||||
|
pass
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
# Mock what happens inside the "with"
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
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(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, "send", self.fake_send))
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
||||||
|
with less_console_noise():
|
||||||
|
# Start the connection pool
|
||||||
|
registry.start_connection_pool()
|
||||||
# Kill the connection pool
|
# Kill the connection pool
|
||||||
registry.kill_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
|
# An exception should be raised as end user will be informed
|
||||||
|
@ -227,6 +236,8 @@ class TestConnectionPool(TestCase):
|
||||||
# The number of open pools should match the number of requested ones.
|
# The number of open pools should match the number of requested ones.
|
||||||
# If it is 0, then they failed to open
|
# If it is 0, then they failed to open
|
||||||
self.assertEqual(len(registry._pool.conn), self.pool_options["size"])
|
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)
|
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
||||||
def test_raises_connection_error(self):
|
def test_raises_connection_error(self):
|
||||||
|
@ -236,6 +247,9 @@ class TestConnectionPool(TestCase):
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
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(Socket, "connect", self.fake_client))
|
||||||
|
with less_console_noise():
|
||||||
|
# Start the connection pool
|
||||||
|
registry.start_connection_pool()
|
||||||
|
|
||||||
# Pool should be running
|
# Pool should be running
|
||||||
self.assertEqual(registry.pool_status.connection_success, True)
|
self.assertEqual(registry.pool_status.connection_success, True)
|
||||||
|
|
|
@ -85,6 +85,21 @@ class EPPConnectionPool(ConnectionPool):
|
||||||
logger.error(message, exc_info=True)
|
logger.error(message, exc_info=True)
|
||||||
raise PoolError(code=PoolErrorCodes.KEEP_ALIVE_FAILED) from err
|
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:
|
def _create_socket(self, client, login) -> Socket:
|
||||||
"""Creates and returns a socket instance"""
|
"""Creates and returns a socket instance"""
|
||||||
socket = Socket(client, login)
|
socket = Socket(client, login)
|
||||||
|
|
|
@ -678,6 +678,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
"type_of_work",
|
"type_of_work",
|
||||||
"more_organization_information",
|
"more_organization_information",
|
||||||
"domain",
|
"domain",
|
||||||
|
"domain_application",
|
||||||
"submitter",
|
"submitter",
|
||||||
"no_other_contacts_rationale",
|
"no_other_contacts_rationale",
|
||||||
"anything_else",
|
"anything_else",
|
||||||
|
@ -739,7 +740,6 @@ class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdmin(ListHeaderAdmin):
|
class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Custom domain applications admin class."""
|
"""Custom domain applications admin class."""
|
||||||
|
|
||||||
class InvestigatorFilter(admin.SimpleListFilter):
|
class InvestigatorFilter(admin.SimpleListFilter):
|
||||||
|
@ -846,6 +846,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
"creator",
|
"creator",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
"approved_domain",
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
"purpose",
|
"purpose",
|
||||||
"submitter",
|
"submitter",
|
||||||
|
@ -883,14 +884,11 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
if (
|
if (
|
||||||
obj
|
obj
|
||||||
and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED
|
and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED
|
||||||
and (
|
and obj.status != models.DomainApplication.ApplicationStatus.APPROVED
|
||||||
obj.status == models.DomainApplication.ApplicationStatus.REJECTED
|
|
||||||
or obj.status == models.DomainApplication.ApplicationStatus.INELIGIBLE
|
|
||||||
)
|
|
||||||
and not obj.domain_is_not_active()
|
and not obj.domain_is_not_active()
|
||||||
):
|
):
|
||||||
# If an admin tried to set an approved application to
|
# If an admin tried to set an approved application to
|
||||||
# rejected or ineligible and the related domain is already
|
# another status and the related domain is already
|
||||||
# active, shortcut the action and throw a friendly
|
# active, shortcut the action and throw a friendly
|
||||||
# error message. This action would still not go through
|
# error message. This action would still not go through
|
||||||
# shortcut or not as the rules are duplicated on the model,
|
# shortcut or not as the rules are duplicated on the model,
|
||||||
|
|
|
@ -2,7 +2,6 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class RegistrarConfig(AppConfig):
|
class RegistrarConfig(AppConfig):
|
||||||
|
|
||||||
"""Configure signal handling for our registrar Django application."""
|
"""Configure signal handling for our registrar Django application."""
|
||||||
|
|
||||||
name = "registrar"
|
name = "registrar"
|
||||||
|
|
|
@ -18,4 +18,7 @@
|
||||||
left: 1rem !important;
|
left: 1rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.usa-alert__body.margin-left-1 {
|
||||||
|
margin-left: 0.5rem!important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,3 +129,28 @@ abbr[title] {
|
||||||
.flex-end {
|
.flex-end {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only apply this custom wrapping to desktop
|
||||||
|
@include at-media(desktop) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 350px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(tablet) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(mobile) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ a.usa-button--unstyled.disabled-link:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.usa-button--unstyled.disabled-button,
|
.usa-button--unstyled.disabled-button,
|
||||||
.usa-button--unstyled.disabled-link:hover,
|
.usa-button--unstyled.disabled-button:hover,
|
||||||
.usa-button--unstyled.disabled-link:focus {
|
.usa-button--unstyled.disabled-button:focus {
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
|
|
|
@ -26,6 +26,16 @@
|
||||||
padding-bottom: units(2px);
|
padding-bottom: units(2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td .no-click-outline-and-cursor-help {
|
||||||
|
outline: none;
|
||||||
|
cursor: help;
|
||||||
|
use {
|
||||||
|
// USWDS has weird interactions with SVGs regarding tooltips,
|
||||||
|
// and other components. In this event, we need to disable pointer interactions.
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ticket #1510
|
// Ticket #1510
|
||||||
// @include at-media('desktop') {
|
// @include at-media('desktop') {
|
||||||
// th:first-child {
|
// th:first-child {
|
||||||
|
|
|
@ -116,6 +116,10 @@ in the form $setting: value,
|
||||||
$theme-color-success-light: $dhs-green-30,
|
$theme-color-success-light: $dhs-green-30,
|
||||||
$theme-color-success-lighter: $dhs-green-15,
|
$theme-color-success-lighter: $dhs-green-15,
|
||||||
|
|
||||||
|
/*---------------------------
|
||||||
|
## Emergency state
|
||||||
|
----------------------------*/
|
||||||
|
$theme-color-emergency: #FFC3F9,
|
||||||
|
|
||||||
/*---------------------------
|
/*---------------------------
|
||||||
# Input settings
|
# Input settings
|
||||||
|
|
|
@ -16,6 +16,7 @@ $ docker-compose exec app python manage.py shell
|
||||||
```
|
```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import environs
|
import environs
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from cfenv import AppEnv # type: ignore
|
from cfenv import AppEnv # type: ignore
|
||||||
|
|
|
@ -74,7 +74,7 @@ urlpatterns = [
|
||||||
views.ApplicationWithdrawn.as_view(),
|
views.ApplicationWithdrawn.as_view(),
|
||||||
name="application-withdrawn",
|
name="application-withdrawn",
|
||||||
),
|
),
|
||||||
path("health/", views.health),
|
path("health", views.health, name="health"),
|
||||||
path("openid/", include("djangooidc.urls")),
|
path("openid/", include("djangooidc.urls")),
|
||||||
path("request/", include((application_urls, APPLICATION_NAMESPACE))),
|
path("request/", include((application_urls, APPLICATION_NAMESPACE))),
|
||||||
path("api/v1/available/", available, name="available"),
|
path("api/v1/available/", available, name="available"),
|
||||||
|
|
|
@ -104,7 +104,7 @@ class DomainApplicationFixture:
|
||||||
# Random choice of agency for selects, used as placeholders for testing.
|
# Random choice of agency for selects, used as placeholders for testing.
|
||||||
else random.choice(DomainApplication.AGENCIES) # nosec
|
else random.choice(DomainApplication.AGENCIES) # nosec
|
||||||
)
|
)
|
||||||
|
da.submission_date = fake.date()
|
||||||
da.federal_type = (
|
da.federal_type = (
|
||||||
app["federal_type"]
|
app["federal_type"]
|
||||||
if "federal_type" in app
|
if "federal_type" in app
|
||||||
|
@ -201,7 +201,6 @@ class DomainApplicationFixture:
|
||||||
|
|
||||||
|
|
||||||
class DomainFixture(DomainApplicationFixture):
|
class DomainFixture(DomainApplicationFixture):
|
||||||
|
|
||||||
"""Create one domain and permissions on it for each user."""
|
"""Create one domain and permissions on it for each user."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Loads files from /tmp into our sandboxes"""
|
"""Loads files from /tmp into our sandboxes"""
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Generates current-full.csv and current-federal.csv then uploads them to the desired URL."""
|
"""Generates current-full.csv and current-federal.csv then uploads them to the desired URL."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Generates current-full.csv and current-federal.csv then uploads them to the desired URL."""
|
"""Generates current-full.csv and current-federal.csv then uploads them to the desired URL."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""Loops through each valid DomainInformation object and updates its agency value"""
|
"""Loops through each valid DomainInformation object and updates its agency value"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import csv
|
import csv
|
||||||
import logging
|
import logging
|
||||||
|
|
|
@ -5,6 +5,7 @@ Regarding our dataclasses:
|
||||||
Not intended to be used as models but rather as an alternative to storing as a dictionary.
|
Not intended to be used as models but rather as an alternative to storing as a dictionary.
|
||||||
By keeping it as a dataclass instead of a dictionary, we can maintain data consistency.
|
By keeping it as a dataclass instead of a dictionary, we can maintain data consistency.
|
||||||
""" # noqa
|
""" # noqa
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
""""""
|
""""""
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 4.2.7 on 2024-02-14 21:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0068_domainapplication_notes_domaininformation_notes"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="email",
|
||||||
|
field=models.EmailField(blank=True, db_index=True, max_length=254, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="first_name",
|
||||||
|
field=models.TextField(blank=True, db_index=True, null=True, verbose_name="first name / given name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="last_name",
|
||||||
|
field=models.TextField(blank=True, db_index=True, null=True, verbose_name="last name / family name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="middle_name",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="phone",
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(
|
||||||
|
blank=True, db_index=True, max_length=128, null=True, region=None
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="title",
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name="title or role in your organization"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,7 +6,6 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
class Contact(TimeStampedModel):
|
class Contact(TimeStampedModel):
|
||||||
|
|
||||||
"""Contact information follows a similar pattern for each contact."""
|
"""Contact information follows a similar pattern for each contact."""
|
||||||
|
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
|
@ -19,38 +18,32 @@ class Contact(TimeStampedModel):
|
||||||
first_name = models.TextField(
|
first_name = models.TextField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="First name",
|
|
||||||
verbose_name="first name / given name",
|
verbose_name="first name / given name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
middle_name = models.TextField(
|
middle_name = models.TextField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Middle name (optional)",
|
|
||||||
)
|
)
|
||||||
last_name = models.TextField(
|
last_name = models.TextField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Last name",
|
|
||||||
verbose_name="last name / family name",
|
verbose_name="last name / family name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
title = models.TextField(
|
title = models.TextField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Title",
|
|
||||||
verbose_name="title or role in your organization",
|
verbose_name="title or role in your organization",
|
||||||
)
|
)
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Email",
|
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Phone",
|
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from django.utils import timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from registrar.models.host import Host
|
from registrar.models.host import Host
|
||||||
from registrar.models.host_ip import HostIP
|
from registrar.models.host_ip import HostIP
|
||||||
|
from registrar.utility.enums import DefaultEmail
|
||||||
|
|
||||||
from registrar.utility.errors import (
|
from registrar.utility.errors import (
|
||||||
ActionNotAllowed,
|
ActionNotAllowed,
|
||||||
|
@ -139,6 +140,24 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# previously existed but has been deleted from the registry
|
# previously existed but has been deleted from the registry
|
||||||
DELETED = "deleted", "Deleted"
|
DELETED = "deleted", "Deleted"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_help_text(cls, state) -> str:
|
||||||
|
"""Returns a help message for a desired state. If none is found, an empty string is returned"""
|
||||||
|
help_texts = {
|
||||||
|
# For now, unknown has the same message as DNS_NEEDED
|
||||||
|
cls.UNKNOWN: ("Before this domain can be used, " "you’ll need to add name server addresses."),
|
||||||
|
cls.DNS_NEEDED: ("Before this domain can be used, " "you’ll need to add name server addresses."),
|
||||||
|
cls.READY: "This domain has name servers and is ready for use.",
|
||||||
|
cls.ON_HOLD: (
|
||||||
|
"This domain is administratively paused, "
|
||||||
|
"so it can’t be edited and won’t resolve in DNS. "
|
||||||
|
"Contact help@get.gov for details."
|
||||||
|
),
|
||||||
|
cls.DELETED: ("This domain has been removed and " "is no longer registered to your organization."),
|
||||||
|
}
|
||||||
|
|
||||||
|
return help_texts.get(state, "")
|
||||||
|
|
||||||
class Cache(property):
|
class Cache(property):
|
||||||
"""
|
"""
|
||||||
Python descriptor to turn class methods into properties.
|
Python descriptor to turn class methods into properties.
|
||||||
|
@ -1399,6 +1418,21 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.info("Changing to DNS_NEEDED state")
|
logger.info("Changing to DNS_NEEDED state")
|
||||||
logger.info("able to transition to DNS_NEEDED state")
|
logger.info("able to transition to DNS_NEEDED state")
|
||||||
|
|
||||||
|
def get_state_help_text(self) -> str:
|
||||||
|
"""Returns a str containing additional information about a given state.
|
||||||
|
Returns custom content for when the domain itself is expired."""
|
||||||
|
|
||||||
|
if self.is_expired() and self.state != self.State.UNKNOWN:
|
||||||
|
# Given expired is not a physical state, but it is displayed as such,
|
||||||
|
# We need custom logic to determine this message.
|
||||||
|
help_text = (
|
||||||
|
"This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
help_text = Domain.State.get_help_text(self.state)
|
||||||
|
|
||||||
|
return help_text
|
||||||
|
|
||||||
def _disclose_fields(self, contact: PublicContact):
|
def _disclose_fields(self, contact: PublicContact):
|
||||||
"""creates a disclose object that can be added to a contact Create using
|
"""creates a disclose object that can be added to a contact Create using
|
||||||
.disclose= <this function> on the command before sending.
|
.disclose= <this function> on the command before sending.
|
||||||
|
@ -1406,7 +1440,9 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
|
is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
|
||||||
DF = epp.DiscloseField
|
DF = epp.DiscloseField
|
||||||
fields = {DF.EMAIL}
|
fields = {DF.EMAIL}
|
||||||
disclose = is_security and contact.email != PublicContact.get_default_security().email
|
|
||||||
|
hidden_security_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
||||||
|
disclose = is_security and contact.email not in hidden_security_emails
|
||||||
# Delete after testing on other devices
|
# Delete after testing on other devices
|
||||||
logger.info("Updated domain contact %s to disclose: %s", contact.email, disclose)
|
logger.info("Updated domain contact %s to disclose: %s", contact.email, disclose)
|
||||||
# Will only disclose DF.EMAIL if its not the default
|
# Will only disclose DF.EMAIL if its not the default
|
||||||
|
|
|
@ -17,7 +17,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainApplication(TimeStampedModel):
|
class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
"""A registrant's application for a new domain."""
|
"""A registrant's application for a new domain."""
|
||||||
|
|
||||||
# Constants for choice fields
|
# Constants for choice fields
|
||||||
|
@ -97,7 +96,6 @@ class DomainApplication(TimeStampedModel):
|
||||||
ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)"
|
ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)"
|
||||||
|
|
||||||
class OrganizationChoices(models.TextChoices):
|
class OrganizationChoices(models.TextChoices):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Primary organization choices:
|
Primary organization choices:
|
||||||
For use in django admin
|
For use in django admin
|
||||||
|
@ -114,7 +112,6 @@ class DomainApplication(TimeStampedModel):
|
||||||
SCHOOL_DISTRICT = "school_district", "School district"
|
SCHOOL_DISTRICT = "school_district", "School district"
|
||||||
|
|
||||||
class OrganizationChoicesVerbose(models.TextChoices):
|
class OrganizationChoicesVerbose(models.TextChoices):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Secondary organization choices
|
Secondary organization choices
|
||||||
For use in the application form and on the templates
|
For use in the application form and on the templates
|
||||||
|
@ -578,6 +575,19 @@ class DomainApplication(TimeStampedModel):
|
||||||
return not self.approved_domain.is_active()
|
return not self.approved_domain.is_active()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def delete_and_clean_up_domain(self, called_from):
|
||||||
|
try:
|
||||||
|
domain_state = self.approved_domain.state
|
||||||
|
# Only reject if it exists on EPP
|
||||||
|
if domain_state != Domain.State.UNKNOWN:
|
||||||
|
self.approved_domain.deletedInEpp()
|
||||||
|
self.approved_domain.save()
|
||||||
|
self.approved_domain.delete()
|
||||||
|
self.approved_domain = None
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(err)
|
||||||
|
logger.error(f"Can't query an approved domain while attempting {called_from}")
|
||||||
|
|
||||||
def _send_status_update_email(self, new_status, email_template, email_template_subject, send_email=True):
|
def _send_status_update_email(self, new_status, email_template, email_template_subject, send_email=True):
|
||||||
"""Send a status update email to the submitter.
|
"""Send a status update email to the submitter.
|
||||||
|
|
||||||
|
@ -641,6 +651,10 @@ class DomainApplication(TimeStampedModel):
|
||||||
self.submission_date = timezone.now().date()
|
self.submission_date = timezone.now().date()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
# Limit email notifications to transitions from Started and Withdrawn
|
||||||
|
limited_statuses = [self.ApplicationStatus.STARTED, self.ApplicationStatus.WITHDRAWN]
|
||||||
|
|
||||||
|
if self.status in limited_statuses:
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"submission confirmation",
|
"submission confirmation",
|
||||||
"emails/submission_confirmation.txt",
|
"emails/submission_confirmation.txt",
|
||||||
|
@ -657,11 +671,19 @@ class DomainApplication(TimeStampedModel):
|
||||||
ApplicationStatus.INELIGIBLE,
|
ApplicationStatus.INELIGIBLE,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.IN_REVIEW,
|
target=ApplicationStatus.IN_REVIEW,
|
||||||
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def in_review(self):
|
def in_review(self):
|
||||||
"""Investigate an application that has been submitted.
|
"""Investigate an application that has been submitted.
|
||||||
|
|
||||||
This action is logged."""
|
This action is logged.
|
||||||
|
|
||||||
|
As side effects this will delete the domain and domain_information
|
||||||
|
(will cascade) when they exist."""
|
||||||
|
|
||||||
|
if self.status == self.ApplicationStatus.APPROVED:
|
||||||
|
self.delete_and_clean_up_domain("in_review")
|
||||||
|
|
||||||
literal = DomainApplication.ApplicationStatus.IN_REVIEW
|
literal = DomainApplication.ApplicationStatus.IN_REVIEW
|
||||||
# Check if the tuple exists, then grab its value
|
# Check if the tuple exists, then grab its value
|
||||||
in_review = literal if literal is not None else "In Review"
|
in_review = literal if literal is not None else "In Review"
|
||||||
|
@ -676,11 +698,19 @@ class DomainApplication(TimeStampedModel):
|
||||||
ApplicationStatus.INELIGIBLE,
|
ApplicationStatus.INELIGIBLE,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.ACTION_NEEDED,
|
target=ApplicationStatus.ACTION_NEEDED,
|
||||||
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def action_needed(self):
|
def action_needed(self):
|
||||||
"""Send back an application that is under investigation or rejected.
|
"""Send back an application that is under investigation or rejected.
|
||||||
|
|
||||||
This action is logged."""
|
This action is logged.
|
||||||
|
|
||||||
|
As side effects this will delete the domain and domain_information
|
||||||
|
(will cascade) when they exist."""
|
||||||
|
|
||||||
|
if self.status == self.ApplicationStatus.APPROVED:
|
||||||
|
self.delete_and_clean_up_domain("reject_with_prejudice")
|
||||||
|
|
||||||
literal = DomainApplication.ApplicationStatus.ACTION_NEEDED
|
literal = DomainApplication.ApplicationStatus.ACTION_NEEDED
|
||||||
# Check if the tuple is setup correctly, then grab its value
|
# Check if the tuple is setup correctly, then grab its value
|
||||||
action_needed = literal if literal is not None else "Action Needed"
|
action_needed = literal if literal is not None else "Action Needed"
|
||||||
|
@ -735,6 +765,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
)
|
)
|
||||||
def withdraw(self):
|
def withdraw(self):
|
||||||
"""Withdraw an application that has been submitted."""
|
"""Withdraw an application that has been submitted."""
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"withdraw",
|
"withdraw",
|
||||||
"emails/domain_request_withdrawn.txt",
|
"emails/domain_request_withdrawn.txt",
|
||||||
|
@ -752,18 +783,9 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
As side effects this will delete the domain and domain_information
|
As side effects this will delete the domain and domain_information
|
||||||
(will cascade), and send an email notification."""
|
(will cascade), and send an email notification."""
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.APPROVED:
|
if self.status == self.ApplicationStatus.APPROVED:
|
||||||
try:
|
self.delete_and_clean_up_domain("reject")
|
||||||
domain_state = self.approved_domain.state
|
|
||||||
# Only reject if it exists on EPP
|
|
||||||
if domain_state != Domain.State.UNKNOWN:
|
|
||||||
self.approved_domain.deletedInEpp()
|
|
||||||
self.approved_domain.save()
|
|
||||||
self.approved_domain.delete()
|
|
||||||
self.approved_domain = None
|
|
||||||
except Exception as err:
|
|
||||||
logger.error(err)
|
|
||||||
logger.error("Can't query an approved domain while attempting a DA reject()")
|
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"action needed",
|
"action needed",
|
||||||
|
@ -792,17 +814,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
and domain_information (will cascade) when they exist."""
|
and domain_information (will cascade) when they exist."""
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.APPROVED:
|
if self.status == self.ApplicationStatus.APPROVED:
|
||||||
try:
|
self.delete_and_clean_up_domain("reject_with_prejudice")
|
||||||
domain_state = self.approved_domain.state
|
|
||||||
# Only reject if it exists on EPP
|
|
||||||
if domain_state != Domain.State.UNKNOWN:
|
|
||||||
self.approved_domain.deletedInEpp()
|
|
||||||
self.approved_domain.save()
|
|
||||||
self.approved_domain.delete()
|
|
||||||
self.approved_domain = None
|
|
||||||
except Exception as err:
|
|
||||||
logger.error(err)
|
|
||||||
logger.error("Can't query an approved domain while attempting a DA reject_with_prejudice()")
|
|
||||||
|
|
||||||
self.creator.restrict_user()
|
self.creator.restrict_user()
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainInformation(TimeStampedModel):
|
class DomainInformation(TimeStampedModel):
|
||||||
|
|
||||||
"""A registrant's domain information for that domain, exported from
|
"""A registrant's domain information for that domain, exported from
|
||||||
DomainApplication. We use these field from DomainApplication with few exceptions
|
DomainApplication. We use these field from DomainApplication with few exceptions
|
||||||
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
||||||
|
@ -256,6 +255,14 @@ class DomainInformation(TimeStampedModel):
|
||||||
else:
|
else:
|
||||||
da_many_to_many_dict[field] = getattr(domain_application, field).all()
|
da_many_to_many_dict[field] = getattr(domain_application, field).all()
|
||||||
|
|
||||||
|
# This will not happen in normal code flow, but having some redundancy doesn't hurt.
|
||||||
|
# da_dict should not have "id" under any circumstances.
|
||||||
|
# If it does have it, then this indicates that common_fields is overzealous in the data
|
||||||
|
# that it is returning. Try looking in DomainHelper.get_common_fields.
|
||||||
|
if "id" in da_dict:
|
||||||
|
logger.warning("create_from_da() -> Found attribute 'id' when trying to create")
|
||||||
|
da_dict.pop("id", None)
|
||||||
|
|
||||||
# Create a placeholder DomainInformation object
|
# Create a placeholder DomainInformation object
|
||||||
domain_info = DomainInformation(**da_dict)
|
domain_info = DomainInformation(**da_dict)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ from string import ascii_uppercase, ascii_lowercase, digits
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from registrar.utility.enums import DefaultEmail
|
||||||
|
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ class PublicContact(TimeStampedModel):
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="20598-0645",
|
pc="20598-0645",
|
||||||
cc="US",
|
cc="US",
|
||||||
email="dotgov@cisa.dhs.gov",
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
@ -104,7 +106,7 @@ class PublicContact(TimeStampedModel):
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email="dotgov@cisa.dhs.gov",
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
@ -121,7 +123,7 @@ class PublicContact(TimeStampedModel):
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email="dotgov@cisa.dhs.gov",
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
@ -138,7 +140,7 @@ class PublicContact(TimeStampedModel):
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email="dotgov@cisa.dhs.gov",
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,11 +4,9 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
class UserDomainRole(TimeStampedModel):
|
class UserDomainRole(TimeStampedModel):
|
||||||
|
|
||||||
"""This is a linking table that connects a user with a role on a domain."""
|
"""This is a linking table that connects a user with a role on a domain."""
|
||||||
|
|
||||||
class Roles(models.TextChoices):
|
class Roles(models.TextChoices):
|
||||||
|
|
||||||
"""The possible roles are listed here.
|
"""The possible roles are listed here.
|
||||||
|
|
||||||
Implementation of the named roles for allowing particular operations happens
|
Implementation of the named roles for allowing particular operations happens
|
||||||
|
|
|
@ -180,8 +180,8 @@ class DomainHelper:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get a list of the existing fields on model_1 and model_2
|
# 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_1_fields = set(field.name for field in model_1._meta.get_fields() if field.name != "id")
|
||||||
model_2_fields = set(field.name for field in model_2._meta.get_fields() if field != "id")
|
model_2_fields = set(field.name for field in model_2._meta.get_fields() if field.name != "id")
|
||||||
|
|
||||||
# Get the fields that exist on both DomainApplication and DomainInformation
|
# Get the fields that exist on both DomainApplication and DomainInformation
|
||||||
common_fields = model_1_fields & model_2_fields
|
common_fields = model_1_fields & model_2_fields
|
||||||
|
|
|
@ -4,7 +4,6 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
class VerifiedByStaff(TimeStampedModel):
|
class VerifiedByStaff(TimeStampedModel):
|
||||||
|
|
||||||
"""emails that get added to this table will bypass ial2 on login."""
|
"""emails that get added to this table will bypass ial2 on login."""
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
|
|
|
@ -4,7 +4,6 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
class Website(TimeStampedModel):
|
class Website(TimeStampedModel):
|
||||||
|
|
||||||
"""Keep domain names in their own table so that applications can refer to
|
"""Keep domain names in their own table so that applications can refer to
|
||||||
many of them."""
|
many of them."""
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ better caching responses.
|
||||||
|
|
||||||
|
|
||||||
class NoCacheMiddleware:
|
class NoCacheMiddleware:
|
||||||
|
|
||||||
"""Middleware to add a single header to every response."""
|
"""Middleware to add a single header to every response."""
|
||||||
|
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
|
|
|
@ -25,21 +25,39 @@
|
||||||
|
|
||||||
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
||||||
{% block extrastyle %}{{ block.super }}
|
{% block extrastyle %}{{ block.super }}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/styles.css" %}" />
|
<link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block branding %}
|
{% block header %}
|
||||||
<h1 id="site-name"><a href="{% url 'admin:index' %}">.gov admin</a></h1>
|
{% if not IS_PRODUCTION %}
|
||||||
{% if user.is_anonymous %}
|
{% with add_body_class="margin-left-1" %}
|
||||||
|
{% include "includes/non-production-alert.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Djando update: this div will change to header #}
|
||||||
|
<div id="header">
|
||||||
|
<div id="branding">
|
||||||
|
{% block branding %}
|
||||||
|
<h1 id="site-name"><a href="{% url 'admin:index' %}">.gov admin</a></h1>
|
||||||
|
{% if user.is_anonymous %}
|
||||||
{% include "admin/color_theme_toggle.html" %}
|
{% include "admin/color_theme_toggle.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% comment %}
|
</div>
|
||||||
|
{% block usertools %}
|
||||||
|
{% if has_permission %}
|
||||||
|
<div id="user-tools">
|
||||||
|
{% block welcome-msg %}
|
||||||
|
{% translate 'Welcome,' %}
|
||||||
|
<strong>{% firstof user.get_short_name user.get_username %}</strong>.
|
||||||
|
{% endblock %}
|
||||||
|
{% comment %}
|
||||||
This was copied from the 'userlinks' template, with a few minor changes.
|
This was copied from the 'userlinks' template, with a few minor changes.
|
||||||
You can find that here:
|
You can find that here:
|
||||||
https://github.com/django/django/blob/d25f3892114466d689fd6936f79f3bd9a9acc30e/django/contrib/admin/templates/admin/base.html#L59
|
https://github.com/django/django/blob/d25f3892114466d689fd6936f79f3bd9a9acc30e/django/contrib/admin/templates/admin/base.html#L59
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% block userlinks %}
|
{% block userlinks %}
|
||||||
{% if site_url %}
|
{% if site_url %}
|
||||||
<a href="{{ site_url }}">{% translate 'View site' %}</a> /
|
<a href="{{ site_url }}">{% translate 'View site' %}</a> /
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -55,4 +73,9 @@
|
||||||
<a href="{% url 'admin:logout' %}" id="admin-logout-button">{% translate 'Log out' %}</a>
|
<a href="{% url 'admin:logout' %}" id="admin-logout-button">{% translate 'Log out' %}</a>
|
||||||
{% include "admin/color_theme_toggle.html" %}
|
{% include "admin/color_theme_toggle.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block nav-global %}{% endblock %}
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block nav-global %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static form_helpers url_helpers %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div class="grid-container">
|
<div class="grid-container">
|
||||||
<div class="grid-row grid-gap">
|
<div class="grid-row grid-gap">
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
<li class="usa-sidenav__item sidenav__step--locked">
|
<li class="usa-sidenav__item sidenav__step--locked">
|
||||||
<span>
|
<span>
|
||||||
{% if not this_step == steps.current %}
|
{% if not this_step == steps.current %}
|
||||||
|
{% if this_step != "review" %}
|
||||||
<svg class="usa-icon text-green" aria-hidden="true" focsuable="false" role="img" width="24" height="24">
|
<svg class="usa-icon text-green" aria-hidden="true" focsuable="false" role="img" width="24" height="24">
|
||||||
<title id="checked-step__{{forloop.counter}}">Checked mark</title>
|
<title id="checked-step__{{forloop.counter}}">Checked mark</title>
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#check_circle"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#check_circle"></use>
|
||||||
</svg>
|
</svg>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<a href="{% namespaced_url 'application' this_step %}"
|
<a href="{% namespaced_url 'application' this_step %}"
|
||||||
{% if this_step == steps.current %}
|
{% if this_step == steps.current %}
|
||||||
class="usa-current"
|
class="usa-current"
|
||||||
|
|
|
@ -70,6 +70,10 @@
|
||||||
<script src="{% static 'js/uswds.min.js' %}" defer></script>
|
<script src="{% static 'js/uswds.min.js' %}" defer></script>
|
||||||
<a class="usa-skipnav" href="#main-content">Skip to main content</a>
|
<a class="usa-skipnav" href="#main-content">Skip to main content</a>
|
||||||
|
|
||||||
|
{% if not IS_PRODUCTION %}
|
||||||
|
{% include "includes/non-production-alert.html" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<section class="usa-banner" aria-label="Official website of the United States government">
|
<section class="usa-banner" aria-label="Official website of the United States government">
|
||||||
<div class="usa-accordion">
|
<div class="usa-accordion">
|
||||||
<header class="usa-banner__header">
|
<header class="usa-banner__header">
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="usa-button"
|
class="usa-button"
|
||||||
>Add user</button>
|
>Add domain manager</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %} {# domain_content #}
|
{% endblock %} {# domain_content #}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="margin-top-4 tablet:grid-col-10">
|
<div class="margin-top-4 tablet:grid-col-10">
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2{% if not domain.is_expired %}{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} dotgov-status-box--action-need{% endif %}{% endif %}"
|
class="usa-summary-box dotgov-status-box padding-bottom-0 margin-top-3 padding-left-2{% if not domain.is_expired %}{% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} dotgov-status-box--action-need{% endif %}{% endif %}"
|
||||||
role="region"
|
role="region"
|
||||||
aria-labelledby="summary-box-key-information"
|
aria-labelledby="summary-box-key-information"
|
||||||
>
|
>
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
<span class="text-bold text-primary-darker">
|
<span class="text-bold text-primary-darker">
|
||||||
Status:
|
Status:
|
||||||
</span>
|
</span>
|
||||||
|
<span class="text-primary-darker">
|
||||||
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
||||||
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
||||||
Expired
|
Expired
|
||||||
|
@ -25,6 +26,12 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.state|title }}
|
{{ domain.state|title }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{% if domain.get_state_help_text %}
|
||||||
|
<div class="padding-top-1 text-primary-darker">
|
||||||
|
{{ domain.get_state_help_text }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,7 +63,7 @@
|
||||||
{% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Your contact information' value=request.user.contact contact='true' edit_link=url editable=domain.is_editable %}
|
||||||
|
|
||||||
{% url 'domain-security-email' pk=domain.id as url %}
|
{% url 'domain-security-email' pk=domain.id as url %}
|
||||||
{% if security_email is not None and security_email != default_security_email%}
|
{% if security_email is not None and security_email not in hidden_security_emails%}
|
||||||
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url editable=domain.is_editable %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url editable=domain.is_editable %}
|
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url editable=domain.is_editable %}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<h1>Manage your domains</h2>
|
<h1>Manage your domains</h2>
|
||||||
|
|
||||||
|
|
||||||
<p class="margin-top-4">
|
<p class="margin-top-4">
|
||||||
<a href="{% url 'application:' %}" class="usa-button"
|
<a href="{% url 'application:' %}" class="usa-button"
|
||||||
>
|
>
|
||||||
|
@ -56,6 +57,16 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.state|capfirst }}
|
{{ domain.state|capfirst }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<svg
|
||||||
|
class="usa-icon usa-tooltip text-middle margin-bottom-05 text-accent-cool no-click-outline-and-cursor-help"
|
||||||
|
data-position="top"
|
||||||
|
title="{{domain.get_state_help_text}}"
|
||||||
|
focusable="true"
|
||||||
|
aria-label="Status Information"
|
||||||
|
role="tooltip"
|
||||||
|
>
|
||||||
|
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#info_outline"></use>
|
||||||
|
</svg>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url "domain" pk=domain.pk %}">
|
<a href="{% url "domain" pk=domain.pk %}">
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="usa-alert usa-alert--emergency margin-y-0 {% if add_class %}{{ add_class }}{% endif %}">
|
||||||
|
<div class="usa-alert__body {% if add_body_class %}{{ add_body_class }}{% endif %}">
|
||||||
|
<b>Attention:</b> You are on a test site.
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,4 +1,5 @@
|
||||||
"""Custom field helpers for our inputs."""
|
"""Custom field helpers for our inputs."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
|
|
@ -12,6 +12,7 @@ from typing import List, Dict
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model, login
|
from django.contrib.auth import get_user_model, login
|
||||||
|
from django.utils.timezone import make_aware
|
||||||
|
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
Contact,
|
Contact,
|
||||||
|
@ -643,7 +644,7 @@ class MockEppLib(TestCase):
|
||||||
self,
|
self,
|
||||||
id,
|
id,
|
||||||
email,
|
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",
|
pw="thisisnotapassword",
|
||||||
):
|
):
|
||||||
fake = info.InfoContactResultData(
|
fake = info.InfoContactResultData(
|
||||||
|
@ -681,7 +682,7 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
mockDataInfoDomain = fakedEppObject(
|
mockDataInfoDomain = fakedEppObject(
|
||||||
"fakePw",
|
"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)],
|
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
|
||||||
hosts=["fake.host.com"],
|
hosts=["fake.host.com"],
|
||||||
statuses=[
|
statuses=[
|
||||||
|
@ -692,7 +693,7 @@ class MockEppLib(TestCase):
|
||||||
)
|
)
|
||||||
mockDataExtensionDomain = fakedEppObject(
|
mockDataExtensionDomain = fakedEppObject(
|
||||||
"fakePw",
|
"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)],
|
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
|
||||||
hosts=["fake.host.com"],
|
hosts=["fake.host.com"],
|
||||||
statuses=[
|
statuses=[
|
||||||
|
@ -706,7 +707,7 @@ class MockEppLib(TestCase):
|
||||||
)
|
)
|
||||||
InfoDomainWithContacts = fakedEppObject(
|
InfoDomainWithContacts = fakedEppObject(
|
||||||
"fakepw",
|
"fakepw",
|
||||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
contacts=[
|
contacts=[
|
||||||
common.DomainContact(
|
common.DomainContact(
|
||||||
contact="securityContact",
|
contact="securityContact",
|
||||||
|
@ -731,7 +732,7 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
InfoDomainWithDefaultSecurityContact = fakedEppObject(
|
InfoDomainWithDefaultSecurityContact = fakedEppObject(
|
||||||
"fakepw",
|
"fakepw",
|
||||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
contacts=[
|
contacts=[
|
||||||
common.DomainContact(
|
common.DomainContact(
|
||||||
contact="defaultSec",
|
contact="defaultSec",
|
||||||
|
@ -750,7 +751,7 @@ class MockEppLib(TestCase):
|
||||||
)
|
)
|
||||||
InfoDomainWithVerisignSecurityContact = fakedEppObject(
|
InfoDomainWithVerisignSecurityContact = fakedEppObject(
|
||||||
"fakepw",
|
"fakepw",
|
||||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
contacts=[
|
contacts=[
|
||||||
common.DomainContact(
|
common.DomainContact(
|
||||||
contact="defaultVeri",
|
contact="defaultVeri",
|
||||||
|
@ -766,7 +767,7 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
InfoDomainWithDefaultTechnicalContact = fakedEppObject(
|
InfoDomainWithDefaultTechnicalContact = fakedEppObject(
|
||||||
"fakepw",
|
"fakepw",
|
||||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
contacts=[
|
contacts=[
|
||||||
common.DomainContact(
|
common.DomainContact(
|
||||||
contact="defaultTech",
|
contact="defaultTech",
|
||||||
|
@ -791,14 +792,14 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
infoDomainNoContact = fakedEppObject(
|
infoDomainNoContact = fakedEppObject(
|
||||||
"security",
|
"security",
|
||||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
cr_date=make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35)),
|
||||||
contacts=[],
|
contacts=[],
|
||||||
hosts=["fake.host.com"],
|
hosts=["fake.host.com"],
|
||||||
)
|
)
|
||||||
|
|
||||||
infoDomainThreeHosts = fakedEppObject(
|
infoDomainThreeHosts = fakedEppObject(
|
||||||
"my-nameserver.gov",
|
"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=[],
|
contacts=[],
|
||||||
hosts=[
|
hosts=[
|
||||||
"ns1.my-nameserver-1.com",
|
"ns1.my-nameserver-1.com",
|
||||||
|
@ -809,25 +810,25 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
infoDomainNoHost = fakedEppObject(
|
infoDomainNoHost = fakedEppObject(
|
||||||
"my-nameserver.gov",
|
"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=[],
|
contacts=[],
|
||||||
hosts=[],
|
hosts=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
infoDomainTwoHosts = fakedEppObject(
|
infoDomainTwoHosts = fakedEppObject(
|
||||||
"my-nameserver.gov",
|
"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=[],
|
contacts=[],
|
||||||
hosts=["ns1.my-nameserver-1.com", "ns1.my-nameserver-2.com"],
|
hosts=["ns1.my-nameserver-1.com", "ns1.my-nameserver-2.com"],
|
||||||
)
|
)
|
||||||
|
|
||||||
mockDataInfoHosts = fakedEppObject(
|
mockDataInfoHosts = fakedEppObject(
|
||||||
"lastPw",
|
"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")],
|
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 = {
|
addDsData1 = {
|
||||||
"keyTag": 1234,
|
"keyTag": 1234,
|
||||||
"alg": 3,
|
"alg": 3,
|
||||||
|
@ -859,7 +860,7 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
infoDomainHasIP = fakedEppObject(
|
infoDomainHasIP = fakedEppObject(
|
||||||
"nameserverwithip.gov",
|
"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=[
|
contacts=[
|
||||||
common.DomainContact(
|
common.DomainContact(
|
||||||
contact="securityContact",
|
contact="securityContact",
|
||||||
|
@ -884,7 +885,7 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
justNameserver = fakedEppObject(
|
justNameserver = fakedEppObject(
|
||||||
"justnameserver.com",
|
"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=[
|
contacts=[
|
||||||
common.DomainContact(
|
common.DomainContact(
|
||||||
contact="securityContact",
|
contact="securityContact",
|
||||||
|
@ -907,7 +908,7 @@ class MockEppLib(TestCase):
|
||||||
|
|
||||||
infoDomainCheckHostIPCombo = fakedEppObject(
|
infoDomainCheckHostIPCombo = fakedEppObject(
|
||||||
"nameserversubdomain.gov",
|
"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=[],
|
contacts=[],
|
||||||
hosts=[
|
hosts=[
|
||||||
"ns1.nameserversubdomain.gov",
|
"ns1.nameserversubdomain.gov",
|
||||||
|
|
|
@ -234,11 +234,11 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
"""
|
"""
|
||||||
Make sure the short name is displaying in admin on the list page
|
Make sure the short name is displaying in admin on the list page
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
mock_client = MockSESClient()
|
mock_client = MockSESClient()
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
with less_console_noise():
|
|
||||||
application.approve()
|
application.approve()
|
||||||
|
|
||||||
response = self.client.get("/admin/registrar/domain/")
|
response = self.client.get("/admin/registrar/domain/")
|
||||||
|
@ -295,12 +295,12 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
Then a user-friendly success message is returned for displaying on the web
|
Then a user-friendly success message is returned for displaying on the web
|
||||||
And `state` is et to `DELETED`
|
And `state` is et to `DELETED`
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain = create_ready_domain()
|
domain = create_ready_domain()
|
||||||
# Put in client hold
|
# Put in client hold
|
||||||
domain.place_client_hold()
|
domain.place_client_hold()
|
||||||
p = "userpass"
|
p = "userpass"
|
||||||
self.client.login(username="staffuser", password=p)
|
self.client.login(username="staffuser", password=p)
|
||||||
|
|
||||||
# Ensure everything is displaying correctly
|
# Ensure everything is displaying correctly
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -309,7 +309,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, domain.name)
|
self.assertContains(response, domain.name)
|
||||||
self.assertContains(response, "Remove from registry")
|
self.assertContains(response, "Remove from registry")
|
||||||
|
|
||||||
# Test the info dialog
|
# Test the info dialog
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -317,7 +316,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
request.user = self.client
|
request.user = self.client
|
||||||
|
|
||||||
with patch("django.contrib.messages.add_message") as mock_add_message:
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
||||||
self.admin.do_delete_domain(request, domain)
|
self.admin.do_delete_domain(request, domain)
|
||||||
mock_add_message.assert_called_once_with(
|
mock_add_message.assert_called_once_with(
|
||||||
|
@ -327,7 +325,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
extra_tags="",
|
extra_tags="",
|
||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(domain.state, Domain.State.DELETED)
|
self.assertEqual(domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
def test_deletion_ready_fsm_failure(self):
|
def test_deletion_ready_fsm_failure(self):
|
||||||
|
@ -337,10 +334,10 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
Then a user-friendly error message is returned for displaying on the web
|
Then a user-friendly error message is returned for displaying on the web
|
||||||
And `state` is not set to `DELETED`
|
And `state` is not set to `DELETED`
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain = create_ready_domain()
|
domain = create_ready_domain()
|
||||||
p = "userpass"
|
p = "userpass"
|
||||||
self.client.login(username="staffuser", password=p)
|
self.client.login(username="staffuser", password=p)
|
||||||
|
|
||||||
# Ensure everything is displaying correctly
|
# Ensure everything is displaying correctly
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -349,7 +346,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, domain.name)
|
self.assertContains(response, domain.name)
|
||||||
self.assertContains(response, "Remove from registry")
|
self.assertContains(response, "Remove from registry")
|
||||||
|
|
||||||
# Test the error
|
# Test the error
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -357,7 +353,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
request.user = self.client
|
request.user = self.client
|
||||||
|
|
||||||
with patch("django.contrib.messages.add_message") as mock_add_message:
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
||||||
self.admin.do_delete_domain(request, domain)
|
self.admin.do_delete_domain(request, domain)
|
||||||
mock_add_message.assert_called_once_with(
|
mock_add_message.assert_called_once_with(
|
||||||
|
@ -380,12 +375,12 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
Then `commands.DeleteDomain` is sent to the registry
|
Then `commands.DeleteDomain` is sent to the registry
|
||||||
And Domain returns normally without an error dialog
|
And Domain returns normally without an error dialog
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain = create_ready_domain()
|
domain = create_ready_domain()
|
||||||
# Put in client hold
|
# Put in client hold
|
||||||
domain.place_client_hold()
|
domain.place_client_hold()
|
||||||
p = "userpass"
|
p = "userpass"
|
||||||
self.client.login(username="staffuser", password=p)
|
self.client.login(username="staffuser", password=p)
|
||||||
|
|
||||||
# Ensure everything is displaying correctly
|
# Ensure everything is displaying correctly
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -394,7 +389,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, domain.name)
|
self.assertContains(response, domain.name)
|
||||||
self.assertContains(response, "Remove from registry")
|
self.assertContains(response, "Remove from registry")
|
||||||
|
|
||||||
# Test the info dialog
|
# Test the info dialog
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -402,7 +396,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
request.user = self.client
|
request.user = self.client
|
||||||
|
|
||||||
# Delete it once
|
# Delete it once
|
||||||
with patch("django.contrib.messages.add_message") as mock_add_message:
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
||||||
self.admin.do_delete_domain(request, domain)
|
self.admin.do_delete_domain(request, domain)
|
||||||
|
@ -415,7 +408,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(domain.state, Domain.State.DELETED)
|
self.assertEqual(domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
# Try to delete it again
|
# Try to delete it again
|
||||||
# Test the info dialog
|
# Test the info dialog
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
|
@ -424,7 +416,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
request.user = self.client
|
request.user = self.client
|
||||||
|
|
||||||
with patch("django.contrib.messages.add_message") as mock_add_message:
|
with patch("django.contrib.messages.add_message") as mock_add_message:
|
||||||
self.admin.do_delete_domain(request, domain)
|
self.admin.do_delete_domain(request, domain)
|
||||||
mock_add_message.assert_called_once_with(
|
mock_add_message.assert_called_once_with(
|
||||||
|
@ -434,7 +425,6 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
extra_tags="",
|
extra_tags="",
|
||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(domain.state, Domain.State.DELETED)
|
self.assertEqual(domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
@skip("Waiting on epp lib to implement")
|
@skip("Waiting on epp lib to implement")
|
||||||
|
@ -491,6 +481,7 @@ class TestDomainApplicationAdminForm(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@boto3_mocking.patching
|
||||||
class TestDomainApplicationAdmin(MockEppLib):
|
class TestDomainApplicationAdmin(MockEppLib):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
@ -596,83 +587,166 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
# Now let's make sure the long description does not exist
|
# Now let's make sure the long description does not exist
|
||||||
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
||||||
|
|
||||||
@boto3_mocking.patching
|
def transition_state_and_send_email(self, application, status):
|
||||||
def test_save_model_sends_submitted_email(self):
|
"""Helper method for the email test cases."""
|
||||||
# make sure there is no user with this email
|
|
||||||
EMAIL = "mayor@igorville.gov"
|
|
||||||
User.objects.filter(email=EMAIL).delete()
|
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
# Create a mock request
|
||||||
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
|
|
||||||
|
# Modify the application's property
|
||||||
|
application.status = status
|
||||||
|
|
||||||
|
# Use the model admin's save_model method
|
||||||
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
|
def assert_email_is_accurate(self, expected_string, email_index, email_address):
|
||||||
|
"""Helper method for the email test cases.
|
||||||
|
email_index is the index of the email in mock_client."""
|
||||||
|
|
||||||
|
# Access the arguments passed to send_email
|
||||||
|
call_args = self.mock_client.EMAILS_SENT
|
||||||
|
kwargs = call_args[email_index]["kwargs"]
|
||||||
|
|
||||||
|
# Retrieve the email details from the arguments
|
||||||
|
from_email = kwargs.get("FromEmailAddress")
|
||||||
|
to_email = kwargs["Destination"]["ToAddresses"][0]
|
||||||
|
email_content = kwargs["Content"]
|
||||||
|
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
||||||
|
|
||||||
|
# Assert or perform other checks on the email details
|
||||||
|
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
||||||
|
self.assertEqual(to_email, email_address)
|
||||||
|
self.assertIn(expected_string, email_body)
|
||||||
|
|
||||||
|
def test_save_model_sends_submitted_email(self):
|
||||||
|
"""When transitioning to submitted from started or withdrawn on a domain request,
|
||||||
|
an email is sent out.
|
||||||
|
|
||||||
|
When transitioning to submitted from dns needed or in review on a domain request,
|
||||||
|
no email is sent out."""
|
||||||
|
|
||||||
|
# Ensure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application()
|
application = completed_application()
|
||||||
|
|
||||||
# Create a mock request
|
# Test Submitted Status from started
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
self.assert_email_is_accurate("We received your .gov domain request.", 0, EMAIL)
|
||||||
# Modify the application's property
|
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
|
||||||
call_args = self.mock_client.EMAILS_SENT
|
|
||||||
kwargs = call_args[0]["kwargs"]
|
|
||||||
|
|
||||||
# Retrieve the email details from the arguments
|
|
||||||
from_email = kwargs.get("FromEmailAddress")
|
|
||||||
to_email = kwargs["Destination"]["ToAddresses"][0]
|
|
||||||
email_content = kwargs["Content"]
|
|
||||||
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
|
||||||
|
|
||||||
# Assert or perform other checks on the email details
|
|
||||||
expected_string = "We received your .gov domain request."
|
|
||||||
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
|
||||||
self.assertEqual(to_email, EMAIL)
|
|
||||||
self.assertIn(expected_string, email_body)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||||
|
|
||||||
@boto3_mocking.patching
|
# Test Withdrawn Status
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
self.assert_email_is_accurate(
|
||||||
|
"Your .gov domain request has been withdrawn and will not be reviewed by our team.", 1, EMAIL
|
||||||
|
)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
|
||||||
|
|
||||||
|
# Test Submitted Status Again (from withdrawn)
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
# Move it to IN_REVIEW
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
# Test Submitted Status Again from in IN_REVIEW, no new email should be sent
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
# Move it to IN_REVIEW
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
# Move it to ACTION_NEEDED
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
# Test Submitted Status Again from in ACTION_NEEDED, no new email should be sent
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
def test_save_model_sends_approved_email(self):
|
def test_save_model_sends_approved_email(self):
|
||||||
# make sure there is no user with this email
|
"""When transitioning to approved on a domain request,
|
||||||
|
an email is sent out every time."""
|
||||||
|
|
||||||
|
# Ensure there is no user with this email
|
||||||
EMAIL = "mayor@igorville.gov"
|
EMAIL = "mayor@igorville.gov"
|
||||||
User.objects.filter(email=EMAIL).delete()
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
|
||||||
with less_console_noise():
|
|
||||||
# Create a sample application
|
# Create a sample application
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Test Submitted Status
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 0, EMAIL)
|
||||||
# Modify the application's property
|
|
||||||
application.status = DomainApplication.ApplicationStatus.APPROVED
|
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
|
||||||
call_args = self.mock_client.EMAILS_SENT
|
|
||||||
kwargs = call_args[0]["kwargs"]
|
|
||||||
|
|
||||||
# Retrieve the email details from the arguments
|
|
||||||
from_email = kwargs.get("FromEmailAddress")
|
|
||||||
to_email = kwargs["Destination"]["ToAddresses"][0]
|
|
||||||
email_content = kwargs["Content"]
|
|
||||||
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
|
||||||
|
|
||||||
# Assert or perform other checks on the email details
|
|
||||||
expected_string = "Congratulations! Your .gov domain request has been approved."
|
|
||||||
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
|
||||||
self.assertEqual(to_email, EMAIL)
|
|
||||||
self.assertIn(expected_string, email_body)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||||
|
|
||||||
@boto3_mocking.patching
|
# Test Withdrawn Status
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
self.assert_email_is_accurate("Your .gov domain request has been rejected.", 1, EMAIL)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
|
||||||
|
|
||||||
|
# Test Submitted Status Again (No new email should be sent)
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
def test_save_model_sends_rejected_email(self):
|
||||||
|
"""When transitioning to rejected on a domain request,
|
||||||
|
an email is sent out every time."""
|
||||||
|
|
||||||
|
# Ensure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
|
# Create a sample application
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Test Submitted Status
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
self.assert_email_is_accurate("Your .gov domain request has been rejected.", 0, EMAIL)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||||
|
|
||||||
|
# Test Withdrawn Status
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
self.assert_email_is_accurate("Congratulations! Your .gov domain request has been approved.", 1, EMAIL)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
|
||||||
|
|
||||||
|
# Test Submitted Status Again (No new email should be sent)
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
|
def test_save_model_sends_withdrawn_email(self):
|
||||||
|
"""When transitioning to withdrawn on a domain request,
|
||||||
|
an email is sent out every time."""
|
||||||
|
|
||||||
|
# Ensure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
|
# Create a sample application
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Test Submitted Status
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
self.assert_email_is_accurate(
|
||||||
|
"Your .gov domain request has been withdrawn and will not be reviewed by our team.", 0, EMAIL
|
||||||
|
)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
||||||
|
|
||||||
|
# Test Withdrawn Status
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.SUBMITTED)
|
||||||
|
self.assert_email_is_accurate("We received your .gov domain request.", 1, EMAIL)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 2)
|
||||||
|
|
||||||
|
# Test Submitted Status Again (No new email should be sent)
|
||||||
|
self.transition_state_and_send_email(application, DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||||
|
|
||||||
def test_save_model_sets_approved_domain(self):
|
def test_save_model_sets_approved_domain(self):
|
||||||
# make sure there is no user with this email
|
# make sure there is no user with this email
|
||||||
EMAIL = "mayor@igorville.gov"
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
@ -695,45 +769,6 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
# Test that approved domain exists and equals requested domain
|
# Test that approved domain exists and equals requested domain
|
||||||
self.assertEqual(application.requested_domain.name, application.approved_domain.name)
|
self.assertEqual(application.requested_domain.name, application.approved_domain.name)
|
||||||
|
|
||||||
@boto3_mocking.patching
|
|
||||||
def test_save_model_sends_rejected_email(self):
|
|
||||||
# make sure there is no user with this email
|
|
||||||
EMAIL = "mayor@igorville.gov"
|
|
||||||
User.objects.filter(email=EMAIL).delete()
|
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
|
||||||
with less_console_noise():
|
|
||||||
# Create a sample application
|
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
|
||||||
|
|
||||||
# Create a mock request
|
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
|
||||||
|
|
||||||
# Modify the application's property
|
|
||||||
application.status = DomainApplication.ApplicationStatus.REJECTED
|
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
|
||||||
call_args = self.mock_client.EMAILS_SENT
|
|
||||||
kwargs = call_args[0]["kwargs"]
|
|
||||||
|
|
||||||
# Retrieve the email details from the arguments
|
|
||||||
from_email = kwargs.get("FromEmailAddress")
|
|
||||||
to_email = kwargs["Destination"]["ToAddresses"][0]
|
|
||||||
email_content = kwargs["Content"]
|
|
||||||
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
|
||||||
|
|
||||||
# Assert or perform other checks on the email details
|
|
||||||
expected_string = "Your .gov domain request has been rejected."
|
|
||||||
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
|
||||||
self.assertEqual(to_email, EMAIL)
|
|
||||||
self.assertIn(expected_string, email_body)
|
|
||||||
|
|
||||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 1)
|
|
||||||
|
|
||||||
@boto3_mocking.patching
|
|
||||||
def test_save_model_sets_restricted_status_on_user(self):
|
def test_save_model_sets_restricted_status_on_user(self):
|
||||||
# make sure there is no user with this email
|
# make sure there is no user with this email
|
||||||
EMAIL = "mayor@igorville.gov"
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
@ -817,6 +852,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
"creator",
|
"creator",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
"approved_domain",
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
"purpose",
|
"purpose",
|
||||||
"submitter",
|
"submitter",
|
||||||
|
@ -883,41 +919,13 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
"Cannot edit an application with a restricted creator.",
|
"Cannot edit an application with a restricted creator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@boto3_mocking.patching
|
def trigger_saving_approved_to_another_state(self, domain_is_active, another_state):
|
||||||
def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
|
"""Helper method that triggers domain request state changes from approved to another state,
|
||||||
# Create an instance of the model
|
with an associated domain that can be either active (READY) or not.
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
|
||||||
application.approved_domain = domain
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
# Create a request object with a superuser
|
Used to test errors when saving a change with an active domain, also used to test side effects
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
when saving a change goes through."""
|
||||||
request.user = self.superuser
|
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
|
||||||
def custom_is_active(self):
|
|
||||||
return True # Override to return True
|
|
||||||
|
|
||||||
# Use ExitStack to combine patch contexts
|
|
||||||
with ExitStack() as stack:
|
|
||||||
# Patch Domain.is_active and django.contrib.messages.error simultaneously
|
|
||||||
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
|
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
|
||||||
with less_console_noise():
|
|
||||||
# Simulate saving the model
|
|
||||||
application.status = DomainApplication.ApplicationStatus.REJECTED
|
|
||||||
self.admin.save_model(request, application, None, True)
|
|
||||||
|
|
||||||
# Assert that the error message was called with the correct argument
|
|
||||||
messages.error.assert_called_once_with(
|
|
||||||
request,
|
|
||||||
"This action is not permitted. The domain " + "is already active.",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_side_effects_when_saving_approved_to_rejected(self):
|
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
|
@ -931,19 +939,24 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
return False # Override to return False
|
return domain_is_active # Override to return True
|
||||||
|
|
||||||
# Use ExitStack to combine patch contexts
|
# Use ExitStack to combine patch contexts
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
# Patch Domain.is_active and django.contrib.messages.error simultaneously
|
# Patch Domain.is_active and django.contrib.messages.error simultaneously
|
||||||
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
|
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
stack.enter_context(patch.object(messages, "error"))
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
|
||||||
with less_console_noise():
|
application.status = another_state
|
||||||
# Simulate saving the model
|
|
||||||
application.status = DomainApplication.ApplicationStatus.REJECTED
|
|
||||||
self.admin.save_model(request, application, None, True)
|
self.admin.save_model(request, application, None, True)
|
||||||
|
|
||||||
|
# Assert that the error message was called with the correct argument
|
||||||
|
if domain_is_active:
|
||||||
|
messages.error.assert_called_once_with(
|
||||||
|
request,
|
||||||
|
"This action is not permitted. The domain " + "is already active.",
|
||||||
|
)
|
||||||
|
else:
|
||||||
# Assert that the error message was never called
|
# Assert that the error message was never called
|
||||||
messages.error.assert_not_called()
|
messages.error.assert_not_called()
|
||||||
|
|
||||||
|
@ -957,75 +970,29 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
with self.assertRaises(DomainInformation.DoesNotExist):
|
with self.assertRaises(DomainInformation.DoesNotExist):
|
||||||
domain_information.refresh_from_db()
|
domain_information.refresh_from_db()
|
||||||
|
|
||||||
|
def test_error_when_saving_approved_to_in_review_and_domain_is_active(self):
|
||||||
|
self.trigger_saving_approved_to_another_state(True, DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
def test_error_when_saving_approved_to_action_needed_and_domain_is_active(self):
|
||||||
|
self.trigger_saving_approved_to_another_state(True, DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
|
||||||
|
def test_error_when_saving_approved_to_rejected_and_domain_is_active(self):
|
||||||
|
self.trigger_saving_approved_to_another_state(True, DomainApplication.ApplicationStatus.REJECTED)
|
||||||
|
|
||||||
def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self):
|
def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self):
|
||||||
# Create an instance of the model
|
self.trigger_saving_approved_to_another_state(True, DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
|
||||||
application.approved_domain = domain
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
# Create a request object with a superuser
|
def test_side_effects_when_saving_approved_to_in_review(self):
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
request.user = self.superuser
|
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
def test_side_effects_when_saving_approved_to_action_needed(self):
|
||||||
def custom_is_active(self):
|
self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
return True # Override to return True
|
|
||||||
|
|
||||||
# Use ExitStack to combine patch contexts
|
def test_side_effects_when_saving_approved_to_rejected(self):
|
||||||
with ExitStack() as stack:
|
self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.REJECTED)
|
||||||
# Patch Domain.is_active and django.contrib.messages.error simultaneously
|
|
||||||
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
|
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
|
||||||
|
|
||||||
# Simulate saving the model
|
|
||||||
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
|
||||||
self.admin.save_model(request, application, None, True)
|
|
||||||
|
|
||||||
# Assert that the error message was called with the correct argument
|
|
||||||
messages.error.assert_called_once_with(
|
|
||||||
request,
|
|
||||||
"This action is not permitted. The domain " + "is already active.",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_side_effects_when_saving_approved_to_ineligible(self):
|
def test_side_effects_when_saving_approved_to_ineligible(self):
|
||||||
# Create an instance of the model
|
self.trigger_saving_approved_to_another_state(False, DomainApplication.ApplicationStatus.INELIGIBLE)
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
|
||||||
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
|
||||||
application.approved_domain = domain
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
# Create a request object with a superuser
|
|
||||||
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
|
||||||
request.user = self.superuser
|
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
|
||||||
def custom_is_active(self):
|
|
||||||
return False # Override to return False
|
|
||||||
|
|
||||||
# Use ExitStack to combine patch contexts
|
|
||||||
with ExitStack() as stack:
|
|
||||||
# Patch Domain.is_active and django.contrib.messages.error simultaneously
|
|
||||||
stack.enter_context(patch.object(Domain, "is_active", custom_is_active))
|
|
||||||
stack.enter_context(patch.object(messages, "error"))
|
|
||||||
|
|
||||||
# Simulate saving the model
|
|
||||||
application.status = DomainApplication.ApplicationStatus.INELIGIBLE
|
|
||||||
self.admin.save_model(request, application, None, True)
|
|
||||||
|
|
||||||
# Assert that the error message was never called
|
|
||||||
messages.error.assert_not_called()
|
|
||||||
|
|
||||||
self.assertEqual(application.approved_domain, None)
|
|
||||||
|
|
||||||
# Assert that Domain got Deleted
|
|
||||||
with self.assertRaises(Domain.DoesNotExist):
|
|
||||||
domain.refresh_from_db()
|
|
||||||
|
|
||||||
# Assert that DomainInformation got Deleted
|
|
||||||
with self.assertRaises(DomainInformation.DoesNotExist):
|
|
||||||
domain_information.refresh_from_db()
|
|
||||||
|
|
||||||
def test_has_correct_filters(self):
|
def test_has_correct_filters(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1242,7 +1209,7 @@ class DomainInvitationAdminTest(TestCase):
|
||||||
self.assertContains(response, retrieved_html, count=1)
|
self.assertContains(response, retrieved_html, count=1)
|
||||||
|
|
||||||
|
|
||||||
class DomainInformationAdminTest(TestCase):
|
class TestDomainInformationAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Setup environment for a mock admin user"""
|
"""Setup environment for a mock admin user"""
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
|
@ -1250,6 +1217,7 @@ class DomainInformationAdminTest(TestCase):
|
||||||
self.admin = DomainInformationAdmin(model=DomainInformation, admin_site=self.site)
|
self.admin = DomainInformationAdmin(model=DomainInformation, admin_site=self.site)
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
self.staffuser = create_user()
|
||||||
self.mock_data_generator = AuditedAdminMockData()
|
self.mock_data_generator = AuditedAdminMockData()
|
||||||
|
|
||||||
self.test_helper = GenericTestHelper(
|
self.test_helper = GenericTestHelper(
|
||||||
|
@ -1293,6 +1261,27 @@ class DomainInformationAdminTest(TestCase):
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
User.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):
|
def test_domain_sortable(self):
|
||||||
"""Tests if DomainInformation sorts by domain correctly"""
|
"""Tests if DomainInformation sorts by domain correctly"""
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
|
@ -1457,13 +1446,12 @@ class ListHeaderAdminTest(TestCase):
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
|
||||||
def test_changelist_view(self):
|
def test_changelist_view(self):
|
||||||
|
with less_console_noise():
|
||||||
# Have to get creative to get past linter
|
# Have to get creative to get past linter
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
self.client.login(username="superuser", password=p)
|
self.client.login(username="superuser", password=p)
|
||||||
|
|
||||||
# Mock a user
|
# Mock a user
|
||||||
user = mock_user()
|
user = mock_user()
|
||||||
|
|
||||||
# Make the request using the Client class
|
# Make the request using the Client class
|
||||||
# which handles CSRF
|
# which handles CSRF
|
||||||
# Follow=True handles the redirect
|
# Follow=True handles the redirect
|
||||||
|
@ -1476,7 +1464,6 @@ class ListHeaderAdminTest(TestCase):
|
||||||
},
|
},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assert that the filters and search_query are added to the extra_context
|
# Assert that the filters and search_query are added to the extra_context
|
||||||
self.assertIn("filters", response.context)
|
self.assertIn("filters", response.context)
|
||||||
self.assertIn("search_query", response.context)
|
self.assertIn("search_query", response.context)
|
||||||
|
@ -1496,6 +1483,7 @@ class ListHeaderAdminTest(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_filters(self):
|
def test_get_filters(self):
|
||||||
|
with less_console_noise():
|
||||||
# Create a mock request object
|
# Create a mock request object
|
||||||
request = self.factory.get("/admin/yourmodel/")
|
request = self.factory.get("/admin/yourmodel/")
|
||||||
# Set the GET parameters for testing
|
# Set the GET parameters for testing
|
||||||
|
@ -1506,7 +1494,6 @@ class ListHeaderAdminTest(TestCase):
|
||||||
}
|
}
|
||||||
# Call the get_filters method
|
# Call the get_filters method
|
||||||
filters = self.admin.get_filters(request)
|
filters = self.admin.get_filters(request)
|
||||||
|
|
||||||
# Assert the filters extracted from the request GET
|
# Assert the filters extracted from the request GET
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
filters,
|
filters,
|
||||||
|
@ -1953,9 +1940,8 @@ class ContactAdminTest(TestCase):
|
||||||
def test_change_view_for_joined_contact_five_or_more(self):
|
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.
|
"""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."""
|
Assert that the warning on the contact form lists 5 joins and a '1 more' ellispsis."""
|
||||||
|
with less_console_noise():
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
|
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
# join it to 5 domain requests. The 6th join will be a user.
|
# join it to 5 domain requests. The 6th join will be a user.
|
||||||
contact, _ = Contact.objects.get_or_create(user=self.staffuser)
|
contact, _ = Contact.objects.get_or_create(user=self.staffuser)
|
||||||
|
@ -1964,13 +1950,10 @@ class ContactAdminTest(TestCase):
|
||||||
application3 = completed_application(submitter=contact, name="city3.gov")
|
application3 = completed_application(submitter=contact, name="city3.gov")
|
||||||
application4 = completed_application(submitter=contact, name="city4.gov")
|
application4 = completed_application(submitter=contact, name="city4.gov")
|
||||||
application5 = completed_application(submitter=contact, name="city5.gov")
|
application5 = completed_application(submitter=contact, name="city5.gov")
|
||||||
|
|
||||||
with patch("django.contrib.messages.warning") as mock_warning:
|
with patch("django.contrib.messages.warning") as mock_warning:
|
||||||
# Use the test client to simulate the request
|
# Use the test client to simulate the request
|
||||||
response = self.client.get(reverse("admin:registrar_contact_change", args=[contact.pk]))
|
response = self.client.get(reverse("admin:registrar_contact_change", args=[contact.pk]))
|
||||||
|
logger.debug(mock_warning)
|
||||||
logger.info(mock_warning)
|
|
||||||
|
|
||||||
# Assert that the error message was called with the correct argument
|
# Assert that the error message was called with the correct argument
|
||||||
# Note: The 6th join will be a user.
|
# Note: The 6th join will be a user.
|
||||||
mock_warning.assert_called_once_with(
|
mock_warning.assert_called_once_with(
|
||||||
|
|
31
src/registrar/tests/test_environment_variables_effects.py
Normal file
31
src/registrar/tests/test_environment_variables_effects.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from django.test import Client, TestCase, override_settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
|
||||||
|
class MyTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
username = "test_user"
|
||||||
|
first_name = "First"
|
||||||
|
last_name = "Last"
|
||||||
|
email = "info@example.com"
|
||||||
|
self.user = get_user_model().objects.create(
|
||||||
|
username=username, first_name=first_name, last_name=last_name, email=email
|
||||||
|
)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.user.delete()
|
||||||
|
|
||||||
|
@override_settings(IS_PRODUCTION=True)
|
||||||
|
def test_production_environment(self):
|
||||||
|
"""No banner on prod."""
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertNotContains(home_page, "You are on a test site.")
|
||||||
|
|
||||||
|
@override_settings(IS_PRODUCTION=False)
|
||||||
|
def test_non_production_environment(self):
|
||||||
|
"""Banner on non-prod."""
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "You are on a test site.")
|
|
@ -1,5 +1,6 @@
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
from datetime import date, datetime, time
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ from django.core.management import call_command
|
||||||
from unittest.mock import patch, call
|
from unittest.mock import patch, call
|
||||||
from epplibwrapper import commands, common
|
from epplibwrapper import commands, common
|
||||||
|
|
||||||
from .common import MockEppLib
|
from .common import MockEppLib, less_console_noise
|
||||||
|
|
||||||
|
|
||||||
class TestPopulateFirstReady(TestCase):
|
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)
|
self.unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN)
|
||||||
|
|
||||||
# Set a ready_at date for testing purposes
|
# 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):
|
def tearDown(self):
|
||||||
"""Deletes all DB objects related to migrations"""
|
"""Deletes all DB objects related to migrations"""
|
||||||
|
@ -49,6 +52,7 @@ class TestPopulateFirstReady(TestCase):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the populate_first_ready command with the specified arguments.
|
execute the populate_first_ready command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
with patch(
|
with patch(
|
||||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
@ -59,19 +63,15 @@ class TestPopulateFirstReady(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests that the populate_first_ready works as expected for the state 'ready'
|
Tests that the populate_first_ready works as expected for the state 'ready'
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Set the created at date
|
# Set the created at date
|
||||||
self.ready_domain.created_at = self.ready_at_date
|
self.ready_domain.created_at = self.ready_at_date_tz_aware
|
||||||
self.ready_domain.save()
|
self.ready_domain.save()
|
||||||
|
|
||||||
desired_domain = copy.deepcopy(self.ready_domain)
|
desired_domain = copy.deepcopy(self.ready_domain)
|
||||||
|
|
||||||
desired_domain.first_ready = self.ready_at_date
|
desired_domain.first_ready = self.ready_at_date
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_populate_first_ready()
|
self.run_populate_first_ready()
|
||||||
|
|
||||||
self.assertEqual(desired_domain, self.ready_domain)
|
self.assertEqual(desired_domain, self.ready_domain)
|
||||||
|
|
||||||
# Explicitly test the first_ready date
|
# Explicitly test the first_ready date
|
||||||
first_ready = Domain.objects.filter(name="fakeready.gov").get().first_ready
|
first_ready = Domain.objects.filter(name="fakeready.gov").get().first_ready
|
||||||
self.assertEqual(first_ready, self.ready_at_date)
|
self.assertEqual(first_ready, self.ready_at_date)
|
||||||
|
@ -80,19 +80,15 @@ class TestPopulateFirstReady(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests that the populate_first_ready works as expected for the state 'deleted'
|
Tests that the populate_first_ready works as expected for the state 'deleted'
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Set the created at date
|
# Set the created at date
|
||||||
self.deleted_domain.created_at = self.ready_at_date
|
self.deleted_domain.created_at = self.ready_at_date_tz_aware
|
||||||
self.deleted_domain.save()
|
self.deleted_domain.save()
|
||||||
|
|
||||||
desired_domain = copy.deepcopy(self.deleted_domain)
|
desired_domain = copy.deepcopy(self.deleted_domain)
|
||||||
|
|
||||||
desired_domain.first_ready = self.ready_at_date
|
desired_domain.first_ready = self.ready_at_date
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_populate_first_ready()
|
self.run_populate_first_ready()
|
||||||
|
|
||||||
self.assertEqual(desired_domain, self.deleted_domain)
|
self.assertEqual(desired_domain, self.deleted_domain)
|
||||||
|
|
||||||
# Explicitly test the first_ready date
|
# Explicitly test the first_ready date
|
||||||
first_ready = Domain.objects.filter(name="fakedeleted.gov").get().first_ready
|
first_ready = Domain.objects.filter(name="fakedeleted.gov").get().first_ready
|
||||||
self.assertEqual(first_ready, self.ready_at_date)
|
self.assertEqual(first_ready, self.ready_at_date)
|
||||||
|
@ -101,23 +97,18 @@ class TestPopulateFirstReady(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests that the populate_first_ready doesn't make changes when a domain's state is 'dns_needed'
|
Tests that the populate_first_ready doesn't make changes when a domain's state is 'dns_needed'
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Set the created at date
|
# Set the created at date
|
||||||
self.dns_needed_domain.created_at = self.ready_at_date
|
self.dns_needed_domain.created_at = self.ready_at_date_tz_aware
|
||||||
self.dns_needed_domain.save()
|
self.dns_needed_domain.save()
|
||||||
|
|
||||||
desired_domain = copy.deepcopy(self.dns_needed_domain)
|
desired_domain = copy.deepcopy(self.dns_needed_domain)
|
||||||
|
|
||||||
desired_domain.first_ready = None
|
desired_domain.first_ready = None
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_populate_first_ready()
|
self.run_populate_first_ready()
|
||||||
|
|
||||||
current_domain = self.dns_needed_domain
|
current_domain = self.dns_needed_domain
|
||||||
# The object should largely be unaltered (does not test first_ready)
|
# The object should largely be unaltered (does not test first_ready)
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
|
|
||||||
first_ready = Domain.objects.filter(name="fakedns.gov").get().first_ready
|
first_ready = Domain.objects.filter(name="fakedns.gov").get().first_ready
|
||||||
|
|
||||||
# Explicitly test the first_ready date
|
# Explicitly test the first_ready date
|
||||||
self.assertNotEqual(first_ready, self.ready_at_date)
|
self.assertNotEqual(first_ready, self.ready_at_date)
|
||||||
self.assertEqual(first_ready, None)
|
self.assertEqual(first_ready, None)
|
||||||
|
@ -126,18 +117,15 @@ class TestPopulateFirstReady(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests that the populate_first_ready works as expected for the state 'on_hold'
|
Tests that the populate_first_ready works as expected for the state 'on_hold'
|
||||||
"""
|
"""
|
||||||
self.hold_domain.created_at = self.ready_at_date
|
with less_console_noise():
|
||||||
|
self.hold_domain.created_at = self.ready_at_date_tz_aware
|
||||||
self.hold_domain.save()
|
self.hold_domain.save()
|
||||||
|
|
||||||
desired_domain = copy.deepcopy(self.hold_domain)
|
desired_domain = copy.deepcopy(self.hold_domain)
|
||||||
desired_domain.first_ready = self.ready_at_date
|
desired_domain.first_ready = self.ready_at_date
|
||||||
|
|
||||||
# Run the update first ready_at script
|
# Run the update first ready_at script
|
||||||
self.run_populate_first_ready()
|
self.run_populate_first_ready()
|
||||||
|
|
||||||
current_domain = self.hold_domain
|
current_domain = self.hold_domain
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
|
|
||||||
# Explicitly test the first_ready date
|
# Explicitly test the first_ready date
|
||||||
first_ready = Domain.objects.filter(name="fakehold.gov").get().first_ready
|
first_ready = Domain.objects.filter(name="fakehold.gov").get().first_ready
|
||||||
self.assertEqual(first_ready, self.ready_at_date)
|
self.assertEqual(first_ready, self.ready_at_date)
|
||||||
|
@ -146,21 +134,17 @@ class TestPopulateFirstReady(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests that the populate_first_ready works as expected for the state 'unknown'
|
Tests that the populate_first_ready works as expected for the state 'unknown'
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Set the created at date
|
# Set the created at date
|
||||||
self.unknown_domain.created_at = self.ready_at_date
|
self.unknown_domain.created_at = self.ready_at_date_tz_aware
|
||||||
self.unknown_domain.save()
|
self.unknown_domain.save()
|
||||||
|
|
||||||
desired_domain = copy.deepcopy(self.unknown_domain)
|
desired_domain = copy.deepcopy(self.unknown_domain)
|
||||||
desired_domain.first_ready = None
|
desired_domain.first_ready = None
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_populate_first_ready()
|
self.run_populate_first_ready()
|
||||||
|
|
||||||
current_domain = self.unknown_domain
|
current_domain = self.unknown_domain
|
||||||
|
|
||||||
# The object should largely be unaltered (does not test first_ready)
|
# The object should largely be unaltered (does not test first_ready)
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
|
|
||||||
# Explicitly test the first_ready date
|
# Explicitly test the first_ready date
|
||||||
first_ready = Domain.objects.filter(name="fakeunknown.gov").get().first_ready
|
first_ready = Domain.objects.filter(name="fakeunknown.gov").get().first_ready
|
||||||
self.assertNotEqual(first_ready, self.ready_at_date)
|
self.assertNotEqual(first_ready, self.ready_at_date)
|
||||||
|
@ -185,6 +169,7 @@ class TestPatchAgencyInfo(TestCase):
|
||||||
@patch("registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", return_value=True)
|
@patch("registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", return_value=True)
|
||||||
def call_patch_federal_agency_info(self, mock_prompt):
|
def call_patch_federal_agency_info(self, mock_prompt):
|
||||||
"""Calls the patch_federal_agency_info command and mimics a keypress"""
|
"""Calls the patch_federal_agency_info command and mimics a keypress"""
|
||||||
|
with less_console_noise():
|
||||||
call_command("patch_federal_agency_info", "registrar/tests/data/fake_current_full.csv", debug=True)
|
call_command("patch_federal_agency_info", "registrar/tests/data/fake_current_full.csv", debug=True)
|
||||||
|
|
||||||
def test_patch_agency_info(self):
|
def test_patch_agency_info(self):
|
||||||
|
@ -194,15 +179,12 @@ class TestPatchAgencyInfo(TestCase):
|
||||||
of a `DomainInformation` object when the corresponding
|
of a `DomainInformation` object when the corresponding
|
||||||
`TransitionDomain` object has a valid `federal_agency`.
|
`TransitionDomain` object has a valid `federal_agency`.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Ensure that the federal_agency is None
|
# Ensure that the federal_agency is None
|
||||||
self.assertEqual(self.domain_info.federal_agency, None)
|
self.assertEqual(self.domain_info.federal_agency, None)
|
||||||
|
|
||||||
self.call_patch_federal_agency_info()
|
self.call_patch_federal_agency_info()
|
||||||
|
|
||||||
# Reload the domain_info object from the database
|
# Reload the domain_info object from the database
|
||||||
self.domain_info.refresh_from_db()
|
self.domain_info.refresh_from_db()
|
||||||
|
|
||||||
# Check that the federal_agency field was updated
|
# Check that the federal_agency field was updated
|
||||||
self.assertEqual(self.domain_info.federal_agency, "test agency")
|
self.assertEqual(self.domain_info.federal_agency, "test agency")
|
||||||
|
|
||||||
|
@ -213,19 +195,16 @@ class TestPatchAgencyInfo(TestCase):
|
||||||
of a `DomainInformation` object when the corresponding
|
of a `DomainInformation` object when the corresponding
|
||||||
`TransitionDomain` object does not exist.
|
`TransitionDomain` object does not exist.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Set federal_agency to None to simulate a skip
|
# Set federal_agency to None to simulate a skip
|
||||||
self.transition_domain.federal_agency = None
|
self.transition_domain.federal_agency = None
|
||||||
self.transition_domain.save()
|
self.transition_domain.save()
|
||||||
|
|
||||||
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context:
|
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context:
|
||||||
self.call_patch_federal_agency_info()
|
self.call_patch_federal_agency_info()
|
||||||
|
|
||||||
# Check that the correct log message was output
|
# Check that the correct log message was output
|
||||||
self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0])
|
self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0])
|
||||||
|
|
||||||
# Reload the domain_info object from the database
|
# Reload the domain_info object from the database
|
||||||
self.domain_info.refresh_from_db()
|
self.domain_info.refresh_from_db()
|
||||||
|
|
||||||
# Check that the federal_agency field was not updated
|
# Check that the federal_agency field was not updated
|
||||||
self.assertIsNone(self.domain_info.federal_agency)
|
self.assertIsNone(self.domain_info.federal_agency)
|
||||||
|
|
||||||
|
@ -235,23 +214,19 @@ class TestPatchAgencyInfo(TestCase):
|
||||||
updates the DomainInformation object, because a record exists in the
|
updates the DomainInformation object, because a record exists in the
|
||||||
provided current-full.csv file.
|
provided current-full.csv file.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Set federal_agency to None to simulate a skip
|
# Set federal_agency to None to simulate a skip
|
||||||
self.transition_domain.federal_agency = None
|
self.transition_domain.federal_agency = None
|
||||||
self.transition_domain.save()
|
self.transition_domain.save()
|
||||||
|
|
||||||
# Change the domain name to something parsable in the .csv
|
# Change the domain name to something parsable in the .csv
|
||||||
self.domain.name = "cdomain1.gov"
|
self.domain.name = "cdomain1.gov"
|
||||||
self.domain.save()
|
self.domain.save()
|
||||||
|
|
||||||
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context:
|
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="WARNING") as context:
|
||||||
self.call_patch_federal_agency_info()
|
self.call_patch_federal_agency_info()
|
||||||
|
|
||||||
# Check that the correct log message was output
|
# Check that the correct log message was output
|
||||||
self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0])
|
self.assertIn("SOME AGENCY DATA WAS NONE", context.output[0])
|
||||||
|
|
||||||
# Reload the domain_info object from the database
|
# Reload the domain_info object from the database
|
||||||
self.domain_info.refresh_from_db()
|
self.domain_info.refresh_from_db()
|
||||||
|
|
||||||
# Check that the federal_agency field was not updated
|
# Check that the federal_agency field was not updated
|
||||||
self.assertEqual(self.domain_info.federal_agency, "World War I Centennial Commission")
|
self.assertEqual(self.domain_info.federal_agency, "World War I Centennial Commission")
|
||||||
|
|
||||||
|
@ -261,18 +236,15 @@ class TestPatchAgencyInfo(TestCase):
|
||||||
does not update the `federal_agency` field
|
does not update the `federal_agency` field
|
||||||
of a `DomainInformation` object
|
of a `DomainInformation` object
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain_info.federal_agency = "unchanged"
|
self.domain_info.federal_agency = "unchanged"
|
||||||
self.domain_info.save()
|
self.domain_info.save()
|
||||||
|
|
||||||
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="INFO") as context:
|
with self.assertLogs("registrar.management.commands.patch_federal_agency_info", level="INFO") as context:
|
||||||
self.call_patch_federal_agency_info()
|
self.call_patch_federal_agency_info()
|
||||||
|
|
||||||
# Check that the correct log message was output
|
# Check that the correct log message was output
|
||||||
self.assertIn("FINISHED", context.output[1])
|
self.assertIn("FINISHED", context.output[1])
|
||||||
|
|
||||||
# Reload the domain_info object from the database
|
# Reload the domain_info object from the database
|
||||||
self.domain_info.refresh_from_db()
|
self.domain_info.refresh_from_db()
|
||||||
|
|
||||||
# Check that the federal_agency field was not updated
|
# Check that the federal_agency field was not updated
|
||||||
self.assertEqual(self.domain_info.federal_agency, "unchanged")
|
self.assertEqual(self.domain_info.federal_agency, "unchanged")
|
||||||
|
|
||||||
|
@ -283,39 +255,37 @@ class TestExtendExpirationDates(MockEppLib):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
# Create a valid domain that is updatable
|
# Create a valid domain that is updatable
|
||||||
Domain.objects.get_or_create(
|
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(
|
TransitionDomain.objects.get_or_create(
|
||||||
username="testytester@mail.com",
|
username="testytester@mail.com",
|
||||||
domain_name="waterbutpurple.gov",
|
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
|
# Create a domain with an invalid expiration date
|
||||||
Domain.objects.get_or_create(
|
Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY, expiration_date=date(2022, 5, 25))
|
||||||
name="fake.gov", state=Domain.State.READY, expiration_date=datetime.date(2022, 5, 25)
|
|
||||||
)
|
|
||||||
TransitionDomain.objects.get_or_create(
|
TransitionDomain.objects.get_or_create(
|
||||||
username="themoonisactuallycheese@mail.com",
|
username="themoonisactuallycheese@mail.com",
|
||||||
domain_name="fake.gov",
|
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
|
# Create a domain with an invalid state
|
||||||
Domain.objects.get_or_create(
|
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(
|
TransitionDomain.objects.get_or_create(
|
||||||
username="fakeneeded@mail.com",
|
username="fakeneeded@mail.com",
|
||||||
domain_name="fakeneeded.gov",
|
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
|
# Create a domain with a date greater than the maximum
|
||||||
Domain.objects.get_or_create(
|
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(
|
TransitionDomain.objects.get_or_create(
|
||||||
username="fakemaximum@mail.com",
|
username="fakemaximum@mail.com",
|
||||||
domain_name="fakemaximum.gov",
|
domain_name="fakemaximum.gov",
|
||||||
epp_expiration_date=datetime.date(2024, 12, 31),
|
epp_expiration_date=date(2024, 12, 31),
|
||||||
)
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -338,6 +308,7 @@ class TestExtendExpirationDates(MockEppLib):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the extend_expiration_dates command with the specified arguments.
|
execute the extend_expiration_dates command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
with patch(
|
with patch(
|
||||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
@ -348,44 +319,41 @@ class TestExtendExpirationDates(MockEppLib):
|
||||||
"""
|
"""
|
||||||
Tests that the extend_expiration_dates method extends dates as expected
|
Tests that the extend_expiration_dates method extends dates as expected
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
||||||
desired_domain.expiration_date = datetime.date(2024, 11, 15)
|
desired_domain.expiration_date = date(2024, 11, 15)
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_extend_expiration_dates()
|
self.run_extend_expiration_dates()
|
||||||
|
|
||||||
current_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
current_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
||||||
|
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
# Explicitly test the expiration date
|
# Explicitly test the expiration date
|
||||||
self.assertEqual(current_domain.expiration_date, datetime.date(2024, 11, 15))
|
self.assertEqual(current_domain.expiration_date, date(2024, 11, 15))
|
||||||
|
|
||||||
def test_extends_expiration_date_skips_non_current(self):
|
def test_extends_expiration_date_skips_non_current(self):
|
||||||
"""
|
"""
|
||||||
Tests that the extend_expiration_dates method correctly skips domains
|
Tests that the extend_expiration_dates method correctly skips domains
|
||||||
with an expiration date less than a certain threshold.
|
with an expiration date less than a certain threshold.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
desired_domain = Domain.objects.filter(name="fake.gov").get()
|
desired_domain = Domain.objects.filter(name="fake.gov").get()
|
||||||
desired_domain.expiration_date = datetime.date(2022, 5, 25)
|
desired_domain.expiration_date = date(2022, 5, 25)
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_extend_expiration_dates()
|
self.run_extend_expiration_dates()
|
||||||
|
|
||||||
current_domain = Domain.objects.filter(name="fake.gov").get()
|
current_domain = Domain.objects.filter(name="fake.gov").get()
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
|
|
||||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||||
# will skip all dates less than date(2023, 11, 15), meaning that this domain
|
# will skip all dates less than date(2023, 11, 15), meaning that this domain
|
||||||
# should not be affected by the change.
|
# should not be affected by the change.
|
||||||
self.assertEqual(current_domain.expiration_date, datetime.date(2022, 5, 25))
|
self.assertEqual(current_domain.expiration_date, date(2022, 5, 25))
|
||||||
|
|
||||||
def test_extends_expiration_date_skips_maximum_date(self):
|
def test_extends_expiration_date_skips_maximum_date(self):
|
||||||
"""
|
"""
|
||||||
Tests that the extend_expiration_dates method correctly skips domains
|
Tests that the extend_expiration_dates method correctly skips domains
|
||||||
with an expiration date more than a certain threshold.
|
with an expiration date more than a certain threshold.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
desired_domain = Domain.objects.filter(name="fakemaximum.gov").get()
|
desired_domain = Domain.objects.filter(name="fakemaximum.gov").get()
|
||||||
desired_domain.expiration_date = datetime.date(2024, 12, 31)
|
desired_domain.expiration_date = date(2024, 12, 31)
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_extend_expiration_dates()
|
self.run_extend_expiration_dates()
|
||||||
|
@ -396,14 +364,15 @@ class TestExtendExpirationDates(MockEppLib):
|
||||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||||
# will skip all dates less than date(2023, 11, 15), meaning that this domain
|
# will skip all dates less than date(2023, 11, 15), meaning that this domain
|
||||||
# should not be affected by the change.
|
# should not be affected by the change.
|
||||||
self.assertEqual(current_domain.expiration_date, datetime.date(2024, 12, 31))
|
self.assertEqual(current_domain.expiration_date, date(2024, 12, 31))
|
||||||
|
|
||||||
def test_extends_expiration_date_skips_non_ready(self):
|
def test_extends_expiration_date_skips_non_ready(self):
|
||||||
"""
|
"""
|
||||||
Tests that the extend_expiration_dates method correctly skips domains not in the state "ready"
|
Tests that the extend_expiration_dates method correctly skips domains not in the state "ready"
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
desired_domain = Domain.objects.filter(name="fakeneeded.gov").get()
|
desired_domain = Domain.objects.filter(name="fakeneeded.gov").get()
|
||||||
desired_domain.expiration_date = datetime.date(2023, 11, 15)
|
desired_domain.expiration_date = date(2023, 11, 15)
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_extend_expiration_dates()
|
self.run_extend_expiration_dates()
|
||||||
|
@ -414,7 +383,7 @@ class TestExtendExpirationDates(MockEppLib):
|
||||||
# Explicitly test the expiration date. The extend_expiration_dates script
|
# Explicitly test the expiration date. The extend_expiration_dates script
|
||||||
# will skip all dates less than date(2023, 11, 15), meaning that this domain
|
# will skip all dates less than date(2023, 11, 15), meaning that this domain
|
||||||
# should not be affected by the change.
|
# should not be affected by the change.
|
||||||
self.assertEqual(current_domain.expiration_date, datetime.date(2023, 11, 15))
|
self.assertEqual(current_domain.expiration_date, date(2023, 11, 15))
|
||||||
|
|
||||||
def test_extends_expiration_date_idempotent(self):
|
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
|
Verifies that running the method multiple times does not change the expiration date
|
||||||
of a domain beyond the initial extension.
|
of a domain beyond the initial extension.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
desired_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
||||||
desired_domain.expiration_date = datetime.date(2024, 11, 15)
|
desired_domain.expiration_date = date(2024, 11, 15)
|
||||||
|
|
||||||
# Run the expiration date script
|
# Run the expiration date script
|
||||||
self.run_extend_expiration_dates()
|
self.run_extend_expiration_dates()
|
||||||
|
|
||||||
current_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
current_domain = Domain.objects.filter(name="waterbutpurple.gov").get()
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
|
|
||||||
# Explicitly test the expiration date
|
# Explicitly test the expiration date
|
||||||
self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15))
|
self.assertEqual(desired_domain.expiration_date, date(2024, 11, 15))
|
||||||
|
|
||||||
# Run the expiration date script again
|
# Run the expiration date script again
|
||||||
self.run_extend_expiration_dates()
|
self.run_extend_expiration_dates()
|
||||||
|
|
||||||
# The old domain shouldn't have changed
|
# The old domain shouldn't have changed
|
||||||
self.assertEqual(desired_domain, current_domain)
|
self.assertEqual(desired_domain, current_domain)
|
||||||
|
|
||||||
# Explicitly test the expiration date - should be the same
|
# Explicitly test the expiration date - should be the same
|
||||||
self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15))
|
self.assertEqual(desired_domain.expiration_date, date(2024, 11, 15))
|
||||||
|
|
||||||
|
|
||||||
class TestDiscloseEmails(MockEppLib):
|
class TestDiscloseEmails(MockEppLib):
|
||||||
|
@ -461,6 +425,7 @@ class TestDiscloseEmails(MockEppLib):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the disclose_security_emails command.
|
execute the disclose_security_emails command.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
with patch(
|
with patch(
|
||||||
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
@ -472,6 +437,7 @@ class TestDiscloseEmails(MockEppLib):
|
||||||
Tests that command disclose_security_emails runs successfully with
|
Tests that command disclose_security_emails runs successfully with
|
||||||
appropriate EPP calll to UpdateContact.
|
appropriate EPP calll to UpdateContact.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY)
|
domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY)
|
||||||
expectedSecContact = PublicContact.get_default_security()
|
expectedSecContact = PublicContact.get_default_security()
|
||||||
expectedSecContact.domain = domain
|
expectedSecContact.domain = domain
|
||||||
|
|
|
@ -60,23 +60,27 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
def assertNotRaises(self, exception_type):
|
def assertNotRaises(self, exception_type):
|
||||||
"""Helper method for testing allowed transitions."""
|
"""Helper method for testing allowed transitions."""
|
||||||
|
with less_console_noise():
|
||||||
return self.assertRaises(Exception, None, exception_type)
|
return self.assertRaises(Exception, None, exception_type)
|
||||||
|
|
||||||
def test_empty_create_fails(self):
|
def test_empty_create_fails(self):
|
||||||
"""Can't create a completely empty domain application.
|
"""Can't create a completely empty domain application.
|
||||||
NOTE: something about theexception this test raises messes up with the
|
NOTE: something about theexception this test raises messes up with the
|
||||||
atomic block in a custom tearDown method for the parent test class."""
|
atomic block in a custom tearDown method for the parent test class."""
|
||||||
|
with less_console_noise():
|
||||||
with self.assertRaisesRegex(IntegrityError, "creator"):
|
with self.assertRaisesRegex(IntegrityError, "creator"):
|
||||||
DomainApplication.objects.create()
|
DomainApplication.objects.create()
|
||||||
|
|
||||||
def test_minimal_create(self):
|
def test_minimal_create(self):
|
||||||
"""Can create with just a creator."""
|
"""Can create with just a creator."""
|
||||||
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
application = DomainApplication.objects.create(creator=user)
|
application = DomainApplication.objects.create(creator=user)
|
||||||
self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED)
|
self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED)
|
||||||
|
|
||||||
def test_full_create(self):
|
def test_full_create(self):
|
||||||
"""Can create with all fields."""
|
"""Can create with all fields."""
|
||||||
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
contact = Contact.objects.create()
|
contact = Contact.objects.create()
|
||||||
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
||||||
|
@ -107,6 +111,7 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
def test_domain_info(self):
|
def test_domain_info(self):
|
||||||
"""Can create domain info with all fields."""
|
"""Can create domain info with all fields."""
|
||||||
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
contact = Contact.objects.create()
|
contact = Contact.objects.create()
|
||||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
|
@ -133,6 +138,7 @@ class TestDomainApplication(TestCase):
|
||||||
self.assertEqual(information.id, domain.domain_info.id)
|
self.assertEqual(information.id, domain.domain_info.id)
|
||||||
|
|
||||||
def test_status_fsm_submit_fail(self):
|
def test_status_fsm_submit_fail(self):
|
||||||
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
application = DomainApplication.objects.create(creator=user)
|
application = DomainApplication.objects.create(creator=user)
|
||||||
|
|
||||||
|
@ -143,6 +149,7 @@ class TestDomainApplication(TestCase):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
|
||||||
def test_status_fsm_submit_succeed(self):
|
def test_status_fsm_submit_succeed(self):
|
||||||
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=site)
|
application = DomainApplication.objects.create(creator=user, requested_domain=site)
|
||||||
|
@ -154,33 +161,63 @@ class TestDomainApplication(TestCase):
|
||||||
application.submit()
|
application.submit()
|
||||||
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
||||||
|
|
||||||
def test_submit_sends_email(self):
|
def check_email_sent(self, application, msg, action, expected_count):
|
||||||
"""Create an application and submit it and see if email was sent."""
|
"""Check if an email was sent after performing an action."""
|
||||||
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 self.subTest(msg=msg, action=action):
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
# Perform the specified action
|
||||||
|
action_method = getattr(application, action)
|
||||||
|
action_method()
|
||||||
|
|
||||||
# check to see if an email was sent
|
# Check if an email was sent
|
||||||
self.assertGreater(
|
sent_emails = [
|
||||||
len(
|
|
||||||
[
|
|
||||||
email
|
email
|
||||||
for email in MockSESClient.EMAILS_SENT
|
for email in MockSESClient.EMAILS_SENT
|
||||||
if "test@test.gov" in email["kwargs"]["Destination"]["ToAddresses"]
|
if "mayor@igorville.gov" in email["kwargs"]["Destination"]["ToAddresses"]
|
||||||
]
|
]
|
||||||
),
|
self.assertEqual(len(sent_emails), expected_count)
|
||||||
0,
|
|
||||||
)
|
def test_submit_from_started_sends_email(self):
|
||||||
|
msg = "Create an application and submit it and see if email was sent."
|
||||||
|
application = completed_application()
|
||||||
|
self.check_email_sent(application, msg, "submit", 1)
|
||||||
|
|
||||||
|
def test_submit_from_withdrawn_sends_email(self):
|
||||||
|
msg = "Create a withdrawn application and submit it and see if email was sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
||||||
|
self.check_email_sent(application, msg, "submit", 1)
|
||||||
|
|
||||||
|
def test_submit_from_action_needed_does_not_send_email(self):
|
||||||
|
msg = "Create an application with ACTION_NEEDED status and submit it, check if email was not sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
||||||
|
self.check_email_sent(application, msg, "submit", 0)
|
||||||
|
|
||||||
|
def test_submit_from_in_review_does_not_send_email(self):
|
||||||
|
msg = "Create a withdrawn application and submit it and see if email was sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
self.check_email_sent(application, msg, "submit", 0)
|
||||||
|
|
||||||
|
def test_approve_sends_email(self):
|
||||||
|
msg = "Create an application and approve it and see if email was sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
self.check_email_sent(application, msg, "approve", 1)
|
||||||
|
|
||||||
|
def test_withdraw_sends_email(self):
|
||||||
|
msg = "Create an application and withdraw it and see if email was sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
||||||
|
self.check_email_sent(application, msg, "withdraw", 1)
|
||||||
|
|
||||||
|
def test_reject_sends_email(self):
|
||||||
|
msg = "Create an application and reject it and see if email was sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
self.check_email_sent(application, msg, "reject", 1)
|
||||||
|
|
||||||
|
def test_reject_with_prejudice_does_not_send_email(self):
|
||||||
|
msg = "Create an application and reject it with prejudice and see if email was sent."
|
||||||
|
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
||||||
|
self.check_email_sent(application, msg, "reject_with_prejudice", 0)
|
||||||
|
|
||||||
def test_submit_transition_allowed(self):
|
def test_submit_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -268,7 +305,7 @@ class TestDomainApplication(TestCase):
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_application, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
|
@ -286,7 +323,7 @@ class TestDomainApplication(TestCase):
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_application, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for application, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(application=application, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
|
@ -457,6 +494,46 @@ class TestDomainApplication(TestCase):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.reject_with_prejudice()
|
application.reject_with_prejudice()
|
||||||
|
|
||||||
|
def test_transition_not_allowed_approved_in_review_when_domain_is_active(self):
|
||||||
|
"""Create an application with status approved, create a matching domain that
|
||||||
|
is active, and call in_review against transition rules"""
|
||||||
|
|
||||||
|
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
|
||||||
|
self.approved_application.approved_domain = domain
|
||||||
|
self.approved_application.save()
|
||||||
|
|
||||||
|
# Define a custom implementation for is_active
|
||||||
|
def custom_is_active(self):
|
||||||
|
return True # Override to return True
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
|
# Use patch to temporarily replace is_active with the custom implementation
|
||||||
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
|
# Now, when you call is_active on Domain, it will return True
|
||||||
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
|
self.approved_application.in_review()
|
||||||
|
|
||||||
|
def test_transition_not_allowed_approved_action_needed_when_domain_is_active(self):
|
||||||
|
"""Create an application with status approved, create a matching domain that
|
||||||
|
is active, and call action_needed against transition rules"""
|
||||||
|
|
||||||
|
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
|
||||||
|
self.approved_application.approved_domain = domain
|
||||||
|
self.approved_application.save()
|
||||||
|
|
||||||
|
# Define a custom implementation for is_active
|
||||||
|
def custom_is_active(self):
|
||||||
|
return True # Override to return True
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
|
# Use patch to temporarily replace is_active with the custom implementation
|
||||||
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
|
# Now, when you call is_active on Domain, it will return True
|
||||||
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
|
self.approved_application.action_needed()
|
||||||
|
|
||||||
def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
|
def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create an application with status approved, create a matching domain that
|
||||||
is active, and call reject against transition rules"""
|
is active, and call reject against transition rules"""
|
||||||
|
@ -499,21 +576,25 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
def test_has_rationale_returns_true(self):
|
def test_has_rationale_returns_true(self):
|
||||||
"""has_rationale() returns true when an application has no_other_contacts_rationale"""
|
"""has_rationale() returns true when an application has no_other_contacts_rationale"""
|
||||||
|
with less_console_noise():
|
||||||
self.started_application.no_other_contacts_rationale = "You talkin' to me?"
|
self.started_application.no_other_contacts_rationale = "You talkin' to me?"
|
||||||
self.started_application.save()
|
self.started_application.save()
|
||||||
self.assertEquals(self.started_application.has_rationale(), True)
|
self.assertEquals(self.started_application.has_rationale(), True)
|
||||||
|
|
||||||
def test_has_rationale_returns_false(self):
|
def test_has_rationale_returns_false(self):
|
||||||
"""has_rationale() returns false when an application has no no_other_contacts_rationale"""
|
"""has_rationale() returns false when an application has no no_other_contacts_rationale"""
|
||||||
|
with less_console_noise():
|
||||||
self.assertEquals(self.started_application.has_rationale(), False)
|
self.assertEquals(self.started_application.has_rationale(), False)
|
||||||
|
|
||||||
def test_has_other_contacts_returns_true(self):
|
def test_has_other_contacts_returns_true(self):
|
||||||
"""has_other_contacts() returns true when an application has other_contacts"""
|
"""has_other_contacts() returns true when an application has other_contacts"""
|
||||||
|
with less_console_noise():
|
||||||
# completed_application has other contacts by default
|
# completed_application has other contacts by default
|
||||||
self.assertEquals(self.started_application.has_other_contacts(), True)
|
self.assertEquals(self.started_application.has_other_contacts(), True)
|
||||||
|
|
||||||
def test_has_other_contacts_returns_false(self):
|
def test_has_other_contacts_returns_false(self):
|
||||||
"""has_other_contacts() returns false when an application has no other_contacts"""
|
"""has_other_contacts() returns false when an application has no other_contacts"""
|
||||||
|
with less_console_noise():
|
||||||
application = completed_application(
|
application = completed_application(
|
||||||
status=DomainApplication.ApplicationStatus.STARTED, name="no-others.gov", has_other_contacts=False
|
status=DomainApplication.ApplicationStatus.STARTED, name="no-others.gov", has_other_contacts=False
|
||||||
)
|
)
|
||||||
|
@ -549,7 +630,6 @@ class TestPermissions(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestDomainInformation(TestCase):
|
class TestDomainInformation(TestCase):
|
||||||
|
|
||||||
"""Test the DomainInformation model, when approved or otherwise"""
|
"""Test the DomainInformation model, when approved or otherwise"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -602,7 +682,6 @@ class TestDomainInformation(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestInvitations(TestCase):
|
class TestInvitations(TestCase):
|
||||||
|
|
||||||
"""Test the retrieval of invitations."""
|
"""Test the retrieval of invitations."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -3,10 +3,12 @@ Feature being tested: Registry Integration
|
||||||
|
|
||||||
This file tests the various ways in which the registrar interacts with the registry.
|
This file tests the various ways in which the registrar interacts with the registry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from unittest.mock import MagicMock, patch, call
|
from unittest.mock import MagicMock, patch, call
|
||||||
import datetime
|
import datetime
|
||||||
|
from django.utils.timezone import make_aware
|
||||||
from registrar.models import Domain, Host, HostIP
|
from registrar.models import Domain, Host, HostIP
|
||||||
|
|
||||||
from unittest import skip
|
from unittest import skip
|
||||||
|
@ -46,6 +48,7 @@ class TestDomainCache(MockEppLib):
|
||||||
|
|
||||||
def test_cache_sets_resets(self):
|
def test_cache_sets_resets(self):
|
||||||
"""Cache should be set on getter and reset on setter calls"""
|
"""Cache should be set on getter and reset on setter calls"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
# trigger getter
|
# trigger getter
|
||||||
_ = domain.creation_date
|
_ = domain.creation_date
|
||||||
|
@ -75,6 +78,7 @@ class TestDomainCache(MockEppLib):
|
||||||
|
|
||||||
def test_cache_used_when_avail(self):
|
def test_cache_used_when_avail(self):
|
||||||
"""Cache is pulled from if the object has already been accessed"""
|
"""Cache is pulled from if the object has already been accessed"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
cr_date = domain.creation_date
|
cr_date = domain.creation_date
|
||||||
|
|
||||||
|
@ -94,6 +98,7 @@ class TestDomainCache(MockEppLib):
|
||||||
|
|
||||||
def test_cache_nested_elements(self):
|
def test_cache_nested_elements(self):
|
||||||
"""Cache works correctly with the nested objects cache and hosts"""
|
"""Cache works correctly with the nested objects cache and hosts"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
# The contact list will initially contain objects of type 'DomainContact'
|
# The contact list will initially contain objects of type 'DomainContact'
|
||||||
# this is then transformed into PublicContact, and cache should NOT
|
# this is then transformed into PublicContact, and cache should NOT
|
||||||
|
@ -144,6 +149,7 @@ class TestDomainCache(MockEppLib):
|
||||||
|
|
||||||
def test_map_epp_contact_to_public_contact(self):
|
def test_map_epp_contact_to_public_contact(self):
|
||||||
# Tests that the mapper is working how we expect
|
# Tests that the mapper is working how we expect
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="registry.gov")
|
domain, _ = Domain.objects.get_or_create(name="registry.gov")
|
||||||
security = PublicContact.ContactTypeChoices.SECURITY
|
security = PublicContact.ContactTypeChoices.SECURITY
|
||||||
mapped = domain.map_epp_contact_to_public_contact(
|
mapped = domain.map_epp_contact_to_public_contact(
|
||||||
|
@ -206,6 +212,7 @@ class TestDomainCache(MockEppLib):
|
||||||
gets invalid data from EPPLib
|
gets invalid data from EPPLib
|
||||||
Then the function throws the expected ContactErrors
|
Then the function throws the expected ContactErrors
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="registry.gov")
|
domain, _ = Domain.objects.get_or_create(name="registry.gov")
|
||||||
fakedEpp = self.fakedEppObject()
|
fakedEpp = self.fakedEppObject()
|
||||||
invalid_length = fakedEpp.dummyInfoContactResultData(
|
invalid_length = fakedEpp.dummyInfoContactResultData(
|
||||||
|
@ -346,6 +353,7 @@ class TestDomainStatuses(MockEppLib):
|
||||||
|
|
||||||
def test_get_status(self):
|
def test_get_status(self):
|
||||||
"""Domain 'statuses' getter returns statuses by calling epp"""
|
"""Domain 'statuses' getter returns statuses by calling epp"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="chicken-liver.gov")
|
domain, _ = Domain.objects.get_or_create(name="chicken-liver.gov")
|
||||||
# trigger getter
|
# trigger getter
|
||||||
_ = domain.statuses
|
_ = domain.statuses
|
||||||
|
@ -365,6 +373,7 @@ class TestDomainStatuses(MockEppLib):
|
||||||
def test_get_status_returns_empty_list_when_value_error(self):
|
def test_get_status_returns_empty_list_when_value_error(self):
|
||||||
"""Domain 'statuses' getter returns an empty list
|
"""Domain 'statuses' getter returns an empty list
|
||||||
when value error"""
|
when value error"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov")
|
domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov")
|
||||||
|
|
||||||
def side_effect(self):
|
def side_effect(self):
|
||||||
|
@ -398,26 +407,21 @@ class TestDomainStatuses(MockEppLib):
|
||||||
first_ready is set when a domain is first transitioned to READY. It does not get overwritten
|
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.
|
in case the domain gets out of and back into READY.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov", state=Domain.State.DNS_NEEDED)
|
domain, _ = Domain.objects.get_or_create(name="pig-knuckles.gov", state=Domain.State.DNS_NEEDED)
|
||||||
self.assertEqual(domain.first_ready, None)
|
self.assertEqual(domain.first_ready, None)
|
||||||
|
|
||||||
domain.ready()
|
domain.ready()
|
||||||
|
|
||||||
# check that status is READY
|
# check that status is READY
|
||||||
self.assertTrue(domain.is_active())
|
self.assertTrue(domain.is_active())
|
||||||
self.assertNotEqual(domain.first_ready, None)
|
self.assertNotEqual(domain.first_ready, None)
|
||||||
|
|
||||||
# Capture the value of first_ready
|
# Capture the value of first_ready
|
||||||
first_ready = domain.first_ready
|
first_ready = domain.first_ready
|
||||||
|
|
||||||
# change domain status
|
# change domain status
|
||||||
domain.dns_needed()
|
domain.dns_needed()
|
||||||
self.assertFalse(domain.is_active())
|
self.assertFalse(domain.is_active())
|
||||||
|
|
||||||
# change back to READY
|
# change back to READY
|
||||||
domain.ready()
|
domain.ready()
|
||||||
self.assertTrue(domain.is_active())
|
self.assertTrue(domain.is_active())
|
||||||
|
|
||||||
# assert that the value of first_ready has not changed
|
# assert that the value of first_ready has not changed
|
||||||
self.assertEqual(domain.first_ready, first_ready)
|
self.assertEqual(domain.first_ready, first_ready)
|
||||||
|
|
||||||
|
@ -557,13 +561,11 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
Then the domain has a valid security contact with CISA defaults
|
Then the domain has a valid security contact with CISA defaults
|
||||||
And disclose flags are set to keep the email address hidden
|
And disclose flags are set to keep the email address hidden
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# making a domain should make it domain
|
# making a domain should make it domain
|
||||||
expectedSecContact = PublicContact.get_default_security()
|
expectedSecContact = PublicContact.get_default_security()
|
||||||
expectedSecContact.domain = self.domain
|
expectedSecContact.domain = self.domain
|
||||||
|
|
||||||
self.domain.dns_needed_from_unknown()
|
self.domain.dns_needed_from_unknown()
|
||||||
|
|
||||||
self.assertEqual(self.mockedSendFunction.call_count, 8)
|
self.assertEqual(self.mockedSendFunction.call_count, 8)
|
||||||
self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 4)
|
self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 4)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -573,19 +575,16 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
).email,
|
).email,
|
||||||
expectedSecContact.email,
|
expectedSecContact.email,
|
||||||
)
|
)
|
||||||
|
|
||||||
id = PublicContact.objects.get(
|
id = PublicContact.objects.get(
|
||||||
domain=self.domain,
|
domain=self.domain,
|
||||||
contact_type=PublicContact.ContactTypeChoices.SECURITY,
|
contact_type=PublicContact.ContactTypeChoices.SECURITY,
|
||||||
).registry_id
|
).registry_id
|
||||||
|
|
||||||
expectedSecContact.registry_id = id
|
expectedSecContact.registry_id = id
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True)
|
||||||
|
|
||||||
|
@ -598,6 +597,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
And Domain sends `commands.UpdateDomain` to the registry with the newly
|
And Domain sends `commands.UpdateDomain` to the registry with the newly
|
||||||
created contact of type 'security'
|
created contact of type 'security'
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# make a security contact that is a PublicContact
|
# make a security contact that is a PublicContact
|
||||||
# make sure a security email already exists
|
# make sure a security email already exists
|
||||||
self.domain.dns_needed_from_unknown()
|
self.domain.dns_needed_from_unknown()
|
||||||
|
@ -606,24 +606,19 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedSecContact.email = "newEmail@fake.com"
|
expectedSecContact.email = "newEmail@fake.com"
|
||||||
expectedSecContact.registry_id = "456"
|
expectedSecContact.registry_id = "456"
|
||||||
expectedSecContact.name = "Fakey McFakerson"
|
expectedSecContact.name = "Fakey McFakerson"
|
||||||
|
|
||||||
# calls the security contact setter as if you did
|
# calls the security contact setter as if you did
|
||||||
# self.domain.security_contact=expectedSecContact
|
# self.domain.security_contact=expectedSecContact
|
||||||
expectedSecContact.save()
|
expectedSecContact.save()
|
||||||
|
|
||||||
# no longer the default email it should be disclosed
|
# no longer the default email it should be disclosed
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
||||||
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
||||||
)
|
)
|
||||||
|
|
||||||
# check that send has triggered the create command for the contact
|
# check that send has triggered the create command for the contact
|
||||||
receivedSecurityContact = PublicContact.objects.get(
|
receivedSecurityContact = PublicContact.objects.get(
|
||||||
domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY
|
domain=self.domain, contact_type=PublicContact.ContactTypeChoices.SECURITY
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(receivedSecurityContact, expectedSecContact)
|
self.assertEqual(receivedSecurityContact, expectedSecContact)
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedUpdateDomain, cleaned=True)
|
||||||
|
@ -635,15 +630,12 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
to the registry twice with identical data
|
to the registry twice with identical data
|
||||||
Then no errors are raised in Domain
|
Then no errors are raised in Domain
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
security_contact = self.domain.get_default_security_contact()
|
security_contact = self.domain.get_default_security_contact()
|
||||||
security_contact.registry_id = "fail"
|
security_contact.registry_id = "fail"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
|
|
||||||
self.domain.security_contact = security_contact
|
self.domain.security_contact = security_contact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=False)
|
||||||
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
||||||
|
@ -667,8 +659,8 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
And the domain has a valid security contact with CISA defaults
|
And the domain has a valid security contact with CISA defaults
|
||||||
And disclose flags are set to keep the email address hidden
|
And disclose flags are set to keep the email address hidden
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
old_contact = self.domain.get_default_security_contact()
|
old_contact = self.domain.get_default_security_contact()
|
||||||
|
|
||||||
old_contact.registry_id = "fail"
|
old_contact.registry_id = "fail"
|
||||||
old_contact.email = "user.entered@email.com"
|
old_contact.email = "user.entered@email.com"
|
||||||
old_contact.save()
|
old_contact.save()
|
||||||
|
@ -676,7 +668,6 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
new_contact.registry_id = "fail"
|
new_contact.registry_id = "fail"
|
||||||
new_contact.email = ""
|
new_contact.email = ""
|
||||||
self.domain.security_contact = new_contact
|
self.domain.security_contact = new_contact
|
||||||
|
|
||||||
firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose_email=True)
|
firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose_email=True)
|
||||||
updateDomainAddCall = commands.UpdateDomain(
|
updateDomainAddCall = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
|
@ -692,7 +683,6 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
rem=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
rem=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
||||||
)
|
)
|
||||||
|
|
||||||
defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id
|
defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id
|
||||||
default_security = PublicContact.get_default_security()
|
default_security = PublicContact.get_default_security()
|
||||||
default_security.registry_id = defaultSecID
|
default_security.registry_id = defaultSecID
|
||||||
|
@ -701,7 +691,6 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=defaultSecID, type="security")],
|
add=[common.DomainContact(contact=defaultSecID, type="security")],
|
||||||
)
|
)
|
||||||
|
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call(firstCreateContactCall, cleaned=True),
|
call(firstCreateContactCall, cleaned=True),
|
||||||
call(updateDomainAddCall, cleaned=True),
|
call(updateDomainAddCall, cleaned=True),
|
||||||
|
@ -710,7 +699,6 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
call(createDefaultContact, cleaned=True),
|
call(createDefaultContact, cleaned=True),
|
||||||
call(updateDomainWDefault, cleaned=True),
|
call(updateDomainWDefault, cleaned=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True)
|
||||||
|
|
||||||
def test_updates_security_email(self):
|
def test_updates_security_email(self):
|
||||||
|
@ -721,12 +709,12 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
security contact email
|
security contact email
|
||||||
Then Domain sends `commands.UpdateContact` to the registry
|
Then Domain sends `commands.UpdateContact` to the registry
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
security_contact = self.domain.get_default_security_contact()
|
security_contact = self.domain.get_default_security_contact()
|
||||||
security_contact.email = "originalUserEmail@gmail.com"
|
security_contact.email = "originalUserEmail@gmail.com"
|
||||||
security_contact.registry_id = "fail"
|
security_contact.registry_id = "fail"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
||||||
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
||||||
|
@ -735,7 +723,6 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
||||||
updateContact = self._convertPublicContactToEpp(security_contact, disclose_email=True, createContact=False)
|
updateContact = self._convertPublicContactToEpp(security_contact, disclose_email=True, createContact=False)
|
||||||
|
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call(expectedCreateCommand, cleaned=True),
|
call(expectedCreateCommand, cleaned=True),
|
||||||
call(expectedUpdateDomain, cleaned=True),
|
call(expectedUpdateDomain, cleaned=True),
|
||||||
|
@ -751,8 +738,8 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
Registry is unavailable and throws exception when attempting to build cache from
|
Registry is unavailable and throws exception when attempting to build cache from
|
||||||
registry. Security email retrieved from database.
|
registry. Security email retrieved from database.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Use self.domain_contact which has been initialized with existing contacts, including securityContact
|
# 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
|
# call get_security_email to initially set the security_contact_registry_id in the domain model
|
||||||
self.domain_contact.get_security_email()
|
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
|
# invalidate the cache so the next time get_security_email is called, it has to attempt to populate cache
|
||||||
|
@ -765,11 +752,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
# when get_security_email is called, the registry error will force the security contact
|
# 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
|
# to be retrieved using the security_contact_registry_id in the domain model
|
||||||
security_email = self.domain_contact.get_security_email()
|
security_email = self.domain_contact.get_security_email()
|
||||||
|
|
||||||
# assert that the proper security contact was retrieved by testing the email matches expected value
|
# assert that the proper security contact was retrieved by testing the email matches expected value
|
||||||
self.assertEqual(security_email, "security@mail.gov")
|
self.assertEqual(security_email, "security@mail.gov")
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
@ -781,6 +766,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
The mocked data for the EPP calls for the freeman.gov domain returns a security
|
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
|
contact with registry id of securityContact when InfoContact is called
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Use self.domain_contact which has been initialized with existing contacts, including securityContact
|
# 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
|
# force fetch_cache to be called, which will return above documented mocked hosts
|
||||||
|
@ -798,54 +784,45 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
And the field `disclose` is set to false for DF.EMAIL
|
And the field `disclose` is set to false for DF.EMAIL
|
||||||
on all fields except security
|
on all fields except security
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Generates a domain with four existing contacts
|
# Generates a domain with four existing contacts
|
||||||
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
|
|
||||||
# Contact setup
|
# Contact setup
|
||||||
expected_admin = domain.get_default_administrative_contact()
|
expected_admin = domain.get_default_administrative_contact()
|
||||||
expected_admin.email = self.mockAdministrativeContact.email
|
expected_admin.email = self.mockAdministrativeContact.email
|
||||||
|
|
||||||
expected_registrant = domain.get_default_registrant_contact()
|
expected_registrant = domain.get_default_registrant_contact()
|
||||||
expected_registrant.email = self.mockRegistrantContact.email
|
expected_registrant.email = self.mockRegistrantContact.email
|
||||||
|
|
||||||
expected_security = domain.get_default_security_contact()
|
expected_security = domain.get_default_security_contact()
|
||||||
expected_security.email = self.mockSecurityContact.email
|
expected_security.email = self.mockSecurityContact.email
|
||||||
|
|
||||||
expected_tech = domain.get_default_technical_contact()
|
expected_tech = domain.get_default_technical_contact()
|
||||||
expected_tech.email = self.mockTechnicalContact.email
|
expected_tech.email = self.mockTechnicalContact.email
|
||||||
|
|
||||||
domain.administrative_contact = expected_admin
|
domain.administrative_contact = expected_admin
|
||||||
domain.registrant_contact = expected_registrant
|
domain.registrant_contact = expected_registrant
|
||||||
domain.security_contact = expected_security
|
domain.security_contact = expected_security
|
||||||
domain.technical_contact = expected_tech
|
domain.technical_contact = expected_tech
|
||||||
|
|
||||||
contacts = [
|
contacts = [
|
||||||
(expected_admin, domain.administrative_contact),
|
(expected_admin, domain.administrative_contact),
|
||||||
(expected_registrant, domain.registrant_contact),
|
(expected_registrant, domain.registrant_contact),
|
||||||
(expected_security, domain.security_contact),
|
(expected_security, domain.security_contact),
|
||||||
(expected_tech, domain.technical_contact),
|
(expected_tech, domain.technical_contact),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Test for each contact
|
# Test for each contact
|
||||||
for contact in contacts:
|
for contact in contacts:
|
||||||
expected_contact = contact[0]
|
expected_contact = contact[0]
|
||||||
actual_contact = contact[1]
|
actual_contact = contact[1]
|
||||||
is_security = expected_contact.contact_type == "security"
|
is_security = expected_contact.contact_type == "security"
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expected_contact, disclose_email=is_security)
|
expectedCreateCommand = self._convertPublicContactToEpp(expected_contact, disclose_email=is_security)
|
||||||
|
|
||||||
# Should only be disclosed if the type is security, as the email is valid
|
# Should only be disclosed if the type is security, as the email is valid
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
|
|
||||||
# The emails should match on both items
|
# The emails should match on both items
|
||||||
self.assertEqual(expected_contact.email, actual_contact.email)
|
self.assertEqual(expected_contact.email, actual_contact.email)
|
||||||
|
|
||||||
def test_convert_public_contact_to_epp(self):
|
def test_convert_public_contact_to_epp(self):
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
dummy_contact = domain.get_default_security_contact()
|
dummy_contact = domain.get_default_security_contact()
|
||||||
test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__
|
test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__
|
||||||
test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=False).__dict__
|
test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=False).__dict__
|
||||||
|
|
||||||
# Separated for linter
|
# Separated for linter
|
||||||
disclose_email_field = {common.DiscloseField.EMAIL}
|
disclose_email_field = {common.DiscloseField.EMAIL}
|
||||||
expected_disclose = {
|
expected_disclose = {
|
||||||
|
@ -872,7 +849,6 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
"vat": None,
|
"vat": None,
|
||||||
"voice": "+1.8882820870",
|
"voice": "+1.8882820870",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Separated for linter
|
# Separated for linter
|
||||||
expected_not_disclose = {
|
expected_not_disclose = {
|
||||||
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
||||||
|
@ -898,11 +874,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
"vat": None,
|
"vat": None,
|
||||||
"voice": "+1.8882820870",
|
"voice": "+1.8882820870",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set the ids equal, since this value changes
|
# Set the ids equal, since this value changes
|
||||||
test_disclose["id"] = expected_disclose["id"]
|
test_disclose["id"] = expected_disclose["id"]
|
||||||
test_not_disclose["id"] = expected_not_disclose["id"]
|
test_not_disclose["id"] = expected_not_disclose["id"]
|
||||||
|
|
||||||
self.assertEqual(test_disclose, expected_disclose)
|
self.assertEqual(test_disclose, expected_disclose)
|
||||||
self.assertEqual(test_not_disclose, expected_not_disclose)
|
self.assertEqual(test_not_disclose, expected_not_disclose)
|
||||||
|
|
||||||
|
@ -913,14 +887,13 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
Then Domain sends `commands.CreateContact` to the registry
|
Then Domain sends `commands.CreateContact` to the registry
|
||||||
And the field `disclose` is set to false for DF.EMAIL
|
And the field `disclose` is set to false for DF.EMAIL
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov")
|
domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov")
|
||||||
expectedSecContact = PublicContact.get_default_security()
|
expectedSecContact = PublicContact.get_default_security()
|
||||||
expectedSecContact.domain = domain
|
expectedSecContact.domain = domain
|
||||||
expectedSecContact.registry_id = "defaultSec"
|
expectedSecContact.registry_id = "defaultSec"
|
||||||
domain.security_contact = expectedSecContact
|
domain.security_contact = expectedSecContact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting a default email
|
# Confirm that we are getting a default email
|
||||||
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
||||||
|
@ -932,14 +905,13 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
Then Domain sends `commands.CreateContact` to the registry
|
Then Domain sends `commands.CreateContact` to the registry
|
||||||
And the field `disclose` is set to false for DF.EMAIL
|
And the field `disclose` is set to false for DF.EMAIL
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov")
|
domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov")
|
||||||
expectedTechContact = PublicContact.get_default_technical()
|
expectedTechContact = PublicContact.get_default_technical()
|
||||||
expectedTechContact.domain = domain
|
expectedTechContact.domain = domain
|
||||||
expectedTechContact.registry_id = "defaultTech"
|
expectedTechContact.registry_id = "defaultTech"
|
||||||
domain.technical_contact = expectedTechContact
|
domain.technical_contact = expectedTechContact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedTechContact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedTechContact, disclose_email=False)
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting a default email
|
# Confirm that we are getting a default email
|
||||||
self.assertEqual(domain.technical_contact.email, expectedTechContact.email)
|
self.assertEqual(domain.technical_contact.email, expectedTechContact.email)
|
||||||
|
@ -952,14 +924,13 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
Then Domain sends `commands.CreateContact` to the registry
|
Then Domain sends `commands.CreateContact` to the registry
|
||||||
And the field `disclose` is set to true for DF.EMAIL
|
And the field `disclose` is set to true for DF.EMAIL
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
expectedSecContact = PublicContact.get_default_security()
|
expectedSecContact = PublicContact.get_default_security()
|
||||||
expectedSecContact.domain = domain
|
expectedSecContact.domain = domain
|
||||||
expectedSecContact.email = "123@mail.gov"
|
expectedSecContact.email = "123@mail.gov"
|
||||||
domain.security_contact = expectedSecContact
|
domain.security_contact = expectedSecContact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting the desired email
|
# Confirm that we are getting the desired email
|
||||||
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
||||||
|
@ -974,6 +945,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def test_contact_getter_security(self):
|
def test_contact_getter_security(self):
|
||||||
|
with less_console_noise():
|
||||||
security = PublicContact.ContactTypeChoices.SECURITY
|
security = PublicContact.ContactTypeChoices.SECURITY
|
||||||
# Create prexisting object
|
# Create prexisting object
|
||||||
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
||||||
|
@ -981,17 +953,13 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
contact_id="securityContact",
|
contact_id="securityContact",
|
||||||
contact_type=security,
|
contact_type=security,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Checks if we grabbed the correct PublicContact
|
# Checks if we grabbed the correct PublicContact
|
||||||
self.assertEqual(self.domain_contact.security_contact.email, expected_contact.email)
|
self.assertEqual(self.domain_contact.security_contact.email, expected_contact.email)
|
||||||
|
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.security_contact.registry_id,
|
registry_id=self.domain_contact.security_contact.registry_id,
|
||||||
contact_type=security,
|
contact_type=security,
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
self.assertEqual(self.domain_contact.security_contact, expected_contact_db)
|
self.assertEqual(self.domain_contact.security_contact, expected_contact_db)
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
|
@ -1005,21 +973,19 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.assertEqual(cache.get(security), "securityContact")
|
self.assertEqual(cache.get(security), "securityContact")
|
||||||
|
|
||||||
def test_contact_getter_technical(self):
|
def test_contact_getter_technical(self):
|
||||||
|
with less_console_noise():
|
||||||
technical = PublicContact.ContactTypeChoices.TECHNICAL
|
technical = PublicContact.ContactTypeChoices.TECHNICAL
|
||||||
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
||||||
self.mockTechnicalContact,
|
self.mockTechnicalContact,
|
||||||
contact_id="technicalContact",
|
contact_id="technicalContact",
|
||||||
contact_type=technical,
|
contact_type=technical,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(self.domain_contact.technical_contact.email, expected_contact.email)
|
self.assertEqual(self.domain_contact.technical_contact.email, expected_contact.email)
|
||||||
|
|
||||||
# Checks if we grab the correct PublicContact
|
# Checks if we grab the correct PublicContact
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.technical_contact.registry_id,
|
registry_id=self.domain_contact.technical_contact.registry_id,
|
||||||
contact_type=technical,
|
contact_type=technical,
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
# Checks if we grab the correct PublicContact
|
# Checks if we grab the correct PublicContact
|
||||||
self.assertEqual(self.domain_contact.technical_contact, expected_contact_db)
|
self.assertEqual(self.domain_contact.technical_contact, expected_contact_db)
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
@ -1035,20 +1001,18 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.assertEqual(cache.get(technical), "technicalContact")
|
self.assertEqual(cache.get(technical), "technicalContact")
|
||||||
|
|
||||||
def test_contact_getter_administrative(self):
|
def test_contact_getter_administrative(self):
|
||||||
|
with less_console_noise():
|
||||||
administrative = PublicContact.ContactTypeChoices.ADMINISTRATIVE
|
administrative = PublicContact.ContactTypeChoices.ADMINISTRATIVE
|
||||||
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
||||||
self.mockAdministrativeContact,
|
self.mockAdministrativeContact,
|
||||||
contact_id="adminContact",
|
contact_id="adminContact",
|
||||||
contact_type=administrative,
|
contact_type=administrative,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(self.domain_contact.administrative_contact.email, expected_contact.email)
|
self.assertEqual(self.domain_contact.administrative_contact.email, expected_contact.email)
|
||||||
|
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.administrative_contact.registry_id,
|
registry_id=self.domain_contact.administrative_contact.registry_id,
|
||||||
contact_type=administrative,
|
contact_type=administrative,
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
# Checks if we grab the correct PublicContact
|
# Checks if we grab the correct PublicContact
|
||||||
self.assertEqual(self.domain_contact.administrative_contact, expected_contact_db)
|
self.assertEqual(self.domain_contact.administrative_contact, expected_contact_db)
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
@ -1064,19 +1028,17 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.assertEqual(cache.get(administrative), "adminContact")
|
self.assertEqual(cache.get(administrative), "adminContact")
|
||||||
|
|
||||||
def test_contact_getter_registrant(self):
|
def test_contact_getter_registrant(self):
|
||||||
|
with less_console_noise():
|
||||||
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
expected_contact = self.domain.map_epp_contact_to_public_contact(
|
||||||
self.mockRegistrantContact,
|
self.mockRegistrantContact,
|
||||||
contact_id="regContact",
|
contact_id="regContact",
|
||||||
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(self.domain_contact.registrant_contact.email, expected_contact.email)
|
self.assertEqual(self.domain_contact.registrant_contact.email, expected_contact.email)
|
||||||
|
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.registrant_contact.registry_id,
|
registry_id=self.domain_contact.registrant_contact.registry_id,
|
||||||
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
# Checks if we grab the correct PublicContact
|
# Checks if we grab the correct PublicContact
|
||||||
self.assertEqual(self.domain_contact.registrant_contact, expected_contact_db)
|
self.assertEqual(self.domain_contact.registrant_contact, expected_contact_db)
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
@ -1112,6 +1074,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
|
|
||||||
def test_get_nameserver_changes_success_deleted_vals(self):
|
def test_get_nameserver_changes_success_deleted_vals(self):
|
||||||
"""Testing only deleting and no other changes"""
|
"""Testing only deleting and no other changes"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain._cache["hosts"] = [
|
self.domain._cache["hosts"] = [
|
||||||
{"name": "ns1.example.com", "addrs": None},
|
{"name": "ns1.example.com", "addrs": None},
|
||||||
{"name": "ns2.example.com", "addrs": ["1.2.3.4"]},
|
{"name": "ns2.example.com", "addrs": ["1.2.3.4"]},
|
||||||
|
@ -1136,6 +1099,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
|
|
||||||
def test_get_nameserver_changes_success_updated_vals(self):
|
def test_get_nameserver_changes_success_updated_vals(self):
|
||||||
"""Testing only updating no other changes"""
|
"""Testing only updating no other changes"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain._cache["hosts"] = [
|
self.domain._cache["hosts"] = [
|
||||||
{"name": "ns3.my-nameserver.gov", "addrs": ["1.2.3.4"]},
|
{"name": "ns3.my-nameserver.gov", "addrs": ["1.2.3.4"]},
|
||||||
]
|
]
|
||||||
|
@ -1148,7 +1112,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
new_values,
|
new_values,
|
||||||
oldNameservers,
|
oldNameservers,
|
||||||
) = self.domain.getNameserverChanges(newChanges)
|
) = self.domain.getNameserverChanges(newChanges)
|
||||||
|
|
||||||
self.assertEqual(deleted_values, [])
|
self.assertEqual(deleted_values, [])
|
||||||
self.assertEqual(updated_values, [("ns3.my-nameserver.gov", ["1.2.4.5"])])
|
self.assertEqual(updated_values, [("ns3.my-nameserver.gov", ["1.2.4.5"])])
|
||||||
self.assertEqual(new_values, {})
|
self.assertEqual(new_values, {})
|
||||||
|
@ -1158,6 +1121,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_nameserver_changes_success_new_vals(self):
|
def test_get_nameserver_changes_success_new_vals(self):
|
||||||
|
with less_console_noise():
|
||||||
# Testing only creating no other changes
|
# Testing only creating no other changes
|
||||||
self.domain._cache["hosts"] = [
|
self.domain._cache["hosts"] = [
|
||||||
{"name": "ns1.example.com", "addrs": None},
|
{"name": "ns1.example.com", "addrs": None},
|
||||||
|
@ -1193,11 +1157,10 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
And `domain.is_active` returns False
|
And `domain.is_active` returns False
|
||||||
And domain.first_ready is null
|
And domain.first_ready is null
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# set 1 nameserver
|
# set 1 nameserver
|
||||||
nameserver = "ns1.my-nameserver.com"
|
nameserver = "ns1.my-nameserver.com"
|
||||||
self.domain.nameservers = [(nameserver,)]
|
self.domain.nameservers = [(nameserver,)]
|
||||||
|
|
||||||
# when we create a host, we should've updated at the same time
|
# when we create a host, we should've updated at the same time
|
||||||
created_host = commands.CreateHost(nameserver)
|
created_host = commands.CreateHost(nameserver)
|
||||||
update_domain_with_created = commands.UpdateDomain(
|
update_domain_with_created = commands.UpdateDomain(
|
||||||
|
@ -1205,19 +1168,15 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
add=[common.HostObjSet([created_host.name])],
|
add=[common.HostObjSet([created_host.name])],
|
||||||
rem=[],
|
rem=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
# checking if commands were sent (commands have to be sent in order)
|
# checking if commands were sent (commands have to be sent in order)
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
call(created_host, cleaned=True),
|
call(created_host, cleaned=True),
|
||||||
call(update_domain_with_created, cleaned=True),
|
call(update_domain_with_created, cleaned=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls)
|
self.mockedSendFunction.assert_has_calls(expectedCalls)
|
||||||
|
|
||||||
# check that status is still NOT READY
|
# check that status is still NOT READY
|
||||||
# as you have less than 2 nameservers
|
# as you have less than 2 nameservers
|
||||||
self.assertFalse(self.domain.is_active())
|
self.assertFalse(self.domain.is_active())
|
||||||
|
|
||||||
self.assertEqual(self.domain.first_ready, None)
|
self.assertEqual(self.domain.first_ready, None)
|
||||||
|
|
||||||
def test_user_adds_two_nameservers(self):
|
def test_user_adds_two_nameservers(self):
|
||||||
|
@ -1230,14 +1189,12 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
And `domain.is_active` returns True
|
And `domain.is_active` returns True
|
||||||
And domain.first_ready is not null
|
And domain.first_ready is not null
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# set 2 nameservers
|
# set 2 nameservers
|
||||||
self.domain.nameservers = [(self.nameserver1,), (self.nameserver2,)]
|
self.domain.nameservers = [(self.nameserver1,), (self.nameserver2,)]
|
||||||
|
|
||||||
# when you create a host, you also have to update at same time
|
# when you create a host, you also have to update at same time
|
||||||
created_host1 = commands.CreateHost(self.nameserver1)
|
created_host1 = commands.CreateHost(self.nameserver1)
|
||||||
created_host2 = commands.CreateHost(self.nameserver2)
|
created_host2 = commands.CreateHost(self.nameserver2)
|
||||||
|
|
||||||
update_domain_with_created = commands.UpdateDomain(
|
update_domain_with_created = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[
|
add=[
|
||||||
|
@ -1245,7 +1202,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
],
|
],
|
||||||
rem=[],
|
rem=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
infoDomain = commands.InfoDomain(name="my-nameserver.gov", auth_info=None)
|
infoDomain = commands.InfoDomain(name="my-nameserver.gov", auth_info=None)
|
||||||
# checking if commands were sent (commands have to be sent in order)
|
# checking if commands were sent (commands have to be sent in order)
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
|
@ -1254,7 +1210,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
call(created_host2, cleaned=True),
|
call(created_host2, cleaned=True),
|
||||||
call(update_domain_with_created, cleaned=True),
|
call(update_domain_with_created, cleaned=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||||
self.assertEqual(4, self.mockedSendFunction.call_count)
|
self.assertEqual(4, self.mockedSendFunction.call_count)
|
||||||
# check that status is READY
|
# check that status is READY
|
||||||
|
@ -1268,7 +1223,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
When `domain.nameservers` is set to an array of length 14
|
When `domain.nameservers` is set to an array of length 14
|
||||||
Then Domain raises a user-friendly error
|
Then Domain raises a user-friendly error
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# set 13+ nameservers
|
# set 13+ nameservers
|
||||||
nameserver1 = "ns1.cats-are-superior1.com"
|
nameserver1 = "ns1.cats-are-superior1.com"
|
||||||
nameserver2 = "ns1.cats-are-superior2.com"
|
nameserver2 = "ns1.cats-are-superior2.com"
|
||||||
|
@ -1315,7 +1270,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
to the registry
|
to the registry
|
||||||
And `domain.is_active` returns True
|
And `domain.is_active` returns True
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Mock is set to return 3 nameservers on infodomain
|
# Mock is set to return 3 nameservers on infodomain
|
||||||
self.domainWithThreeNS.nameservers = [(self.nameserver1,), (self.nameserver2,)]
|
self.domainWithThreeNS.nameservers = [(self.nameserver1,), (self.nameserver2,)]
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
|
@ -1343,7 +1298,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
),
|
),
|
||||||
call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True),
|
call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||||
self.assertTrue(self.domainWithThreeNS.is_active())
|
self.assertTrue(self.domainWithThreeNS.is_active())
|
||||||
|
|
||||||
|
@ -1357,7 +1311,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
And `domain.is_active` returns False
|
And `domain.is_active` returns False
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domainWithThreeNS.nameservers = [(self.nameserver1,)]
|
self.domainWithThreeNS.nameservers = [(self.nameserver1,)]
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
call(
|
call(
|
||||||
|
@ -1389,7 +1343,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
),
|
),
|
||||||
call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True),
|
call(commands.DeleteHost(name="ns1.cats-are-superior3.com"), cleaned=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||||
self.assertFalse(self.domainWithThreeNS.is_active())
|
self.assertFalse(self.domainWithThreeNS.is_active())
|
||||||
|
|
||||||
|
@ -1403,12 +1356,12 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
And `commands.UpdateDomain` is sent to add #4 and #5 plus remove #2 and #3
|
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
|
And `commands.DeleteHost` is sent to delete #2 and #3
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domainWithThreeNS.nameservers = [
|
self.domainWithThreeNS.nameservers = [
|
||||||
(self.nameserver1,),
|
(self.nameserver1,),
|
||||||
("ns1.cats-are-superior1.com",),
|
("ns1.cats-are-superior1.com",),
|
||||||
("ns1.cats-are-superior2.com",),
|
("ns1.cats-are-superior2.com",),
|
||||||
]
|
]
|
||||||
|
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None),
|
commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None),
|
||||||
|
@ -1453,7 +1406,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||||
self.assertTrue(self.domainWithThreeNS.is_active())
|
self.assertTrue(self.domainWithThreeNS.is_active())
|
||||||
|
|
||||||
|
@ -1465,9 +1417,8 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
with a subdomain of the domain and no IP addresses
|
with a subdomain of the domain and no IP addresses
|
||||||
Then Domain raises a user-friendly error
|
Then Domain raises a user-friendly error
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
dotgovnameserver = "my-nameserver.gov"
|
dotgovnameserver = "my-nameserver.gov"
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
self.domain.nameservers = [(dotgovnameserver,)]
|
self.domain.nameservers = [(dotgovnameserver,)]
|
||||||
|
|
||||||
|
@ -1480,6 +1431,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
with a different IP address(es)
|
with a different IP address(es)
|
||||||
Then `commands.UpdateHost` is sent to the registry
|
Then `commands.UpdateHost` is sent to the registry
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="nameserverwithip.gov", state=Domain.State.READY)
|
domain, _ = Domain.objects.get_or_create(name="nameserverwithip.gov", state=Domain.State.READY)
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
("ns1.nameserverwithip.gov", ["2.3.4.5", "1.2.3.4"]),
|
("ns1.nameserverwithip.gov", ["2.3.4.5", "1.2.3.4"]),
|
||||||
|
@ -1489,7 +1441,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
),
|
),
|
||||||
("ns3.nameserverwithip.gov", ["2.3.4.5"]),
|
("ns3.nameserverwithip.gov", ["2.3.4.5"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(name="nameserverwithip.gov", auth_info=None),
|
commands.InfoDomain(name="nameserverwithip.gov", auth_info=None),
|
||||||
|
@ -1517,7 +1468,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
cleaned=True,
|
cleaned=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||||
self.assertTrue(domain.is_active())
|
self.assertTrue(domain.is_active())
|
||||||
|
|
||||||
|
@ -1529,8 +1479,8 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
which is not a subdomain of the domain and has IP addresses
|
which is not a subdomain of the domain and has IP addresses
|
||||||
Then Domain raises a user-friendly error
|
Then Domain raises a user-friendly error
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
dotgovnameserver = "mynameserverdotgov.gov"
|
dotgovnameserver = "mynameserverdotgov.gov"
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
self.domain.nameservers = [(dotgovnameserver, ["1.2.3"])]
|
self.domain.nameservers = [(dotgovnameserver, ["1.2.3"])]
|
||||||
|
|
||||||
|
@ -1541,14 +1491,13 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
to the registry twice with identical data
|
to the registry twice with identical data
|
||||||
Then no errors are raised in Domain
|
Then no errors are raised in Domain
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Checking that it doesn't create or update even if out of order
|
# Checking that it doesn't create or update even if out of order
|
||||||
self.domainWithThreeNS.nameservers = [
|
self.domainWithThreeNS.nameservers = [
|
||||||
(self.nameserver3,),
|
(self.nameserver3,),
|
||||||
(self.nameserver1,),
|
(self.nameserver1,),
|
||||||
(self.nameserver2,),
|
(self.nameserver2,),
|
||||||
]
|
]
|
||||||
|
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
call(
|
call(
|
||||||
commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None),
|
commands.InfoDomain(name=self.domainWithThreeNS.name, auth_info=None),
|
||||||
|
@ -1558,13 +1507,12 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
call(commands.InfoHost(name="ns1.my-nameserver-2.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.InfoHost(name="ns1.cats-are-superior3.com"), cleaned=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
self.mockedSendFunction.assert_has_calls(expectedCalls, any_order=True)
|
||||||
self.assertEqual(self.mockedSendFunction.call_count, 4)
|
self.assertEqual(self.mockedSendFunction.call_count, 4)
|
||||||
|
|
||||||
def test_is_subdomain_with_no_ip(self):
|
def test_is_subdomain_with_no_ip(self):
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
("ns1.nameserversubdomain.gov",),
|
("ns1.nameserversubdomain.gov",),
|
||||||
|
@ -1572,8 +1520,8 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_not_subdomain_but_has_ip(self):
|
def test_not_subdomain_but_has_ip(self):
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
("ns1.cats-da-best.gov", ["1.2.3.4"]),
|
("ns1.cats-da-best.gov", ["1.2.3.4"]),
|
||||||
|
@ -1581,6 +1529,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_is_subdomain_but_ip_addr_not_valid(self):
|
def test_is_subdomain_but_ip_addr_not_valid(self):
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
|
@ -1592,6 +1541,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
def test_setting_not_allowed(self):
|
def test_setting_not_allowed(self):
|
||||||
"""Scenario: A domain state is not Ready or DNS needed
|
"""Scenario: A domain state is not Ready or DNS needed
|
||||||
then setting nameservers is not allowed"""
|
then setting nameservers is not allowed"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD)
|
domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD)
|
||||||
with self.assertRaises(ActionNotAllowed):
|
with self.assertRaises(ActionNotAllowed):
|
||||||
domain.nameservers = [self.nameserver1, self.nameserver2]
|
domain.nameservers = [self.nameserver1, self.nameserver2]
|
||||||
|
@ -1602,6 +1552,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
Registry is unavailable and throws exception when attempting to build cache from
|
Registry is unavailable and throws exception when attempting to build cache from
|
||||||
registry. Nameservers retrieved from database.
|
registry. Nameservers retrieved from database.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
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
|
# set the host and host_ips directly in the database; this is normally handled through
|
||||||
# fetch_cache
|
# fetch_cache
|
||||||
|
@ -1609,20 +1560,16 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
host_ip, _ = HostIP.objects.get_or_create(host=host, address="1.1.1.1")
|
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):
|
def side_effect(_request, cleaned):
|
||||||
raise RegistryError(code=ErrorCode.COMMAND_FAILED)
|
raise RegistryError(code=ErrorCode.COMMAND_FAILED)
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
nameservers = domain.nameservers
|
nameservers = domain.nameservers
|
||||||
|
|
||||||
self.assertEqual(len(nameservers), 1)
|
self.assertEqual(len(nameservers), 1)
|
||||||
self.assertEqual(nameservers[0][0], "ns1.fake.gov")
|
self.assertEqual(nameservers[0][0], "ns1.fake.gov")
|
||||||
self.assertEqual(nameservers[0][1], ["1.1.1.1"])
|
self.assertEqual(nameservers[0][1], ["1.1.1.1"])
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
def test_nameservers_stored_on_fetch_cache(self):
|
def test_nameservers_stored_on_fetch_cache(self):
|
||||||
|
@ -1633,8 +1580,8 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
of 'fake.host.com' from InfoDomain and an array of 2 IPs: 1.2.3.4 and 2.3.4.5
|
of 'fake.host.com' from InfoDomain and an array of 2 IPs: 1.2.3.4 and 2.3.4.5
|
||||||
from InfoHost
|
from InfoHost
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||||
|
|
||||||
# mock the get_or_create methods for Host and HostIP
|
# 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(
|
with patch.object(Host.objects, "get_or_create") as mock_host_get_or_create, patch.object(
|
||||||
HostIP.objects, "get_or_create"
|
HostIP.objects, "get_or_create"
|
||||||
|
@ -1642,7 +1589,6 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
# Set the return value for the mocks
|
# Set the return value for the mocks
|
||||||
mock_host_get_or_create.return_value = (Host(), True)
|
mock_host_get_or_create.return_value = (Host(), True)
|
||||||
mock_host_ip_get_or_create.return_value = (HostIP(), True)
|
mock_host_ip_get_or_create.return_value = (HostIP(), True)
|
||||||
|
|
||||||
# force fetch_cache to be called, which will return above documented mocked hosts
|
# force fetch_cache to be called, which will return above documented mocked hosts
|
||||||
domain.nameservers
|
domain.nameservers
|
||||||
# assert that the mocks are called
|
# assert that the mocks are called
|
||||||
|
@ -1791,13 +1737,12 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
else:
|
else:
|
||||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||||
domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
|
|
||||||
# get the DNS SEC extension added to the UpdateDomain command and
|
# get the DNS SEC extension added to the UpdateDomain command and
|
||||||
# verify that it is properly sent
|
# verify that it is properly sent
|
||||||
# args[0] is the _request sent to registry
|
# args[0] is the _request sent to registry
|
||||||
|
@ -1835,9 +1780,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
|
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
def test_dnssec_is_idempotent(self):
|
def test_dnssec_is_idempotent(self):
|
||||||
|
@ -1872,12 +1815,11 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
else:
|
else:
|
||||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||||
|
|
||||||
# set the dnssecdata once
|
# set the dnssecdata once
|
||||||
domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
# set the dnssecdata again
|
# set the dnssecdata again
|
||||||
|
@ -1916,9 +1858,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
|
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithDsData.dsData)
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
def test_user_adds_dnssec_data_multiple_dsdata(self):
|
def test_user_adds_dnssec_data_multiple_dsdata(self):
|
||||||
|
@ -1949,12 +1889,11 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
else:
|
else:
|
||||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
|
domain, _ = Domain.objects.get_or_create(name="dnssec-multdsdata.gov")
|
||||||
|
|
||||||
domain.dnssecdata = self.dnssecExtensionWithMultDsData
|
domain.dnssecdata = self.dnssecExtensionWithMultDsData
|
||||||
# get the DNS SEC extension added to the UpdateDomain command
|
# get the DNS SEC extension added to the UpdateDomain command
|
||||||
# and verify that it is properly sent
|
# and verify that it is properly sent
|
||||||
|
@ -1987,9 +1926,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData)
|
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData)
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
def test_user_removes_dnssec_data(self):
|
def test_user_removes_dnssec_data(self):
|
||||||
|
@ -2021,10 +1958,10 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
else:
|
else:
|
||||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov")
|
||||||
# dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
|
# dnssecdata_get_initial = domain.dnssecdata # call to force initial mock
|
||||||
# domain._invalidate_cache()
|
# domain._invalidate_cache()
|
||||||
|
@ -2078,7 +2015,6 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
def test_update_is_unsuccessful(self):
|
def test_update_is_unsuccessful(self):
|
||||||
|
@ -2087,9 +2023,8 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
When an error is returned from epplibwrapper
|
When an error is returned from epplibwrapper
|
||||||
Then a user-friendly error message is returned for displaying on the web
|
Then a user-friendly error message is returned for displaying on the web
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov")
|
domain, _ = Domain.objects.get_or_create(name="dnssec-invalid.gov")
|
||||||
|
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error())
|
self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error())
|
||||||
|
@ -2117,11 +2052,13 @@ class TestExpirationDate(MockEppLib):
|
||||||
|
|
||||||
def test_expiration_date_setter_not_implemented(self):
|
def test_expiration_date_setter_not_implemented(self):
|
||||||
"""assert that the setter for expiration date is not implemented and will raise error"""
|
"""assert that the setter for expiration date is not implemented and will raise error"""
|
||||||
|
with less_console_noise():
|
||||||
with self.assertRaises(NotImplementedError):
|
with self.assertRaises(NotImplementedError):
|
||||||
self.domain.registry_expiration_date = datetime.date.today()
|
self.domain.registry_expiration_date = datetime.date.today()
|
||||||
|
|
||||||
def test_renew_domain(self):
|
def test_renew_domain(self):
|
||||||
"""assert that the renew_domain sets new expiration date in cache and saves to registrar"""
|
"""assert that the renew_domain sets new expiration date in cache and saves to registrar"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain.renew_domain()
|
self.domain.renew_domain()
|
||||||
test_date = datetime.date(2023, 5, 25)
|
test_date = datetime.date(2023, 5, 25)
|
||||||
self.assertEquals(self.domain._cache["ex_date"], test_date)
|
self.assertEquals(self.domain._cache["ex_date"], test_date)
|
||||||
|
@ -2129,28 +2066,31 @@ class TestExpirationDate(MockEppLib):
|
||||||
|
|
||||||
def test_renew_domain_error(self):
|
def test_renew_domain_error(self):
|
||||||
"""assert that the renew_domain raises an exception when registry raises error"""
|
"""assert that the renew_domain raises an exception when registry raises error"""
|
||||||
|
with less_console_noise():
|
||||||
with self.assertRaises(RegistryError):
|
with self.assertRaises(RegistryError):
|
||||||
self.domain_w_error.renew_domain()
|
self.domain_w_error.renew_domain()
|
||||||
|
|
||||||
def test_is_expired(self):
|
def test_is_expired(self):
|
||||||
"""assert that is_expired returns true for expiration_date in past"""
|
"""assert that is_expired returns true for expiration_date in past"""
|
||||||
|
with less_console_noise():
|
||||||
# force fetch_cache to be called
|
# force fetch_cache to be called
|
||||||
self.domain.statuses
|
self.domain.statuses
|
||||||
self.assertTrue(self.domain.is_expired)
|
self.assertTrue(self.domain.is_expired)
|
||||||
|
|
||||||
def test_is_not_expired(self):
|
def test_is_not_expired(self):
|
||||||
"""assert that is_expired returns false for expiration in future"""
|
"""assert that is_expired returns false for expiration in future"""
|
||||||
|
with less_console_noise():
|
||||||
# to do this, need to mock value returned from timezone.now
|
# to do this, need to mock value returned from timezone.now
|
||||||
# set now to 2023-01-01
|
# set now to 2023-01-01
|
||||||
mocked_datetime = datetime.datetime(2023, 1, 1, 12, 0, 0)
|
mocked_datetime = datetime.datetime(2023, 1, 1, 12, 0, 0)
|
||||||
# force fetch_cache which sets the expiration date to 2023-05-25
|
# force fetch_cache which sets the expiration date to 2023-05-25
|
||||||
self.domain.statuses
|
self.domain.statuses
|
||||||
|
|
||||||
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
|
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
|
||||||
self.assertFalse(self.domain.is_expired())
|
self.assertFalse(self.domain.is_expired())
|
||||||
|
|
||||||
def test_expiration_date_updated_on_info_domain_call(self):
|
def test_expiration_date_updated_on_info_domain_call(self):
|
||||||
"""assert that expiration date in db is updated on info domain call"""
|
"""assert that expiration date in db is updated on info domain call"""
|
||||||
|
with less_console_noise():
|
||||||
# force fetch_cache to be called
|
# force fetch_cache to be called
|
||||||
self.domain.statuses
|
self.domain.statuses
|
||||||
test_date = datetime.date(2023, 5, 25)
|
test_date = datetime.date(2023, 5, 25)
|
||||||
|
@ -2169,7 +2109,7 @@ class TestCreationDate(MockEppLib):
|
||||||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||||
# creation_date returned from mockDataInfoDomain with creation date:
|
# creation_date returned from mockDataInfoDomain with creation date:
|
||||||
# cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35)
|
# 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):
|
def tearDown(self):
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
|
@ -2212,6 +2152,7 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
When `domain.place_client_hold()` is called
|
When `domain.place_client_hold()` is called
|
||||||
Then `CLIENT_HOLD` is added to the domain's statuses
|
Then `CLIENT_HOLD` is added to the domain's statuses
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain.place_client_hold()
|
self.domain.place_client_hold()
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
|
@ -2243,6 +2184,7 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
When `domain.place_client_hold()` is called
|
When `domain.place_client_hold()` is called
|
||||||
Then Domain returns normally (without error)
|
Then Domain returns normally (without error)
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain_on_hold.place_client_hold()
|
self.domain_on_hold.place_client_hold()
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
|
@ -2274,6 +2216,7 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
When `domain.remove_client_hold()` is called
|
When `domain.remove_client_hold()` is called
|
||||||
Then `CLIENT_HOLD` is no longer in the domain's statuses
|
Then `CLIENT_HOLD` is no longer in the domain's statuses
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain_on_hold.revert_client_hold()
|
self.domain_on_hold.revert_client_hold()
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
|
@ -2305,6 +2248,7 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
When `domain.remove_client_hold()` is called
|
When `domain.remove_client_hold()` is called
|
||||||
Then Domain returns normally (without error)
|
Then Domain returns normally (without error)
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.domain.revert_client_hold()
|
self.domain.revert_client_hold()
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
|
@ -2339,17 +2283,16 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
def side_effect(_request, cleaned):
|
def side_effect(_request, cleaned):
|
||||||
raise RegistryError(code=ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION)
|
raise RegistryError(code=ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION)
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
mocked_send = patcher.start()
|
mocked_send = patcher.start()
|
||||||
mocked_send.side_effect = side_effect
|
mocked_send.side_effect = side_effect
|
||||||
|
|
||||||
# if RegistryError is raised, admin formats user-friendly
|
# if RegistryError is raised, admin formats user-friendly
|
||||||
# error message if error is_client_error, is_session_error, or
|
# error message if error is_client_error, is_session_error, or
|
||||||
# is_server_error; so test for those conditions
|
# is_server_error; so test for those conditions
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
self.domain.place_client_hold()
|
self.domain.place_client_hold()
|
||||||
self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error())
|
self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error())
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@ -2443,6 +2386,7 @@ class TestAnalystDelete(MockEppLib):
|
||||||
|
|
||||||
The deleted date is set.
|
The deleted date is set.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Put the domain in client hold
|
# Put the domain in client hold
|
||||||
self.domain.place_client_hold()
|
self.domain.place_client_hold()
|
||||||
# Delete it...
|
# Delete it...
|
||||||
|
@ -2456,16 +2400,12 @@ class TestAnalystDelete(MockEppLib):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Domain itself should not be deleted
|
# Domain itself should not be deleted
|
||||||
self.assertNotEqual(self.domain, None)
|
self.assertNotEqual(self.domain, None)
|
||||||
|
|
||||||
# Domain should have the right state
|
# Domain should have the right state
|
||||||
self.assertEqual(self.domain.state, Domain.State.DELETED)
|
self.assertEqual(self.domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
# Domain should have a deleted
|
# Domain should have a deleted
|
||||||
self.assertNotEqual(self.domain.deleted, None)
|
self.assertNotEqual(self.domain.deleted, None)
|
||||||
|
|
||||||
# Cache should be invalidated
|
# Cache should be invalidated
|
||||||
self.assertEqual(self.domain._cache, {})
|
self.assertEqual(self.domain._cache, {})
|
||||||
|
|
||||||
|
@ -2476,11 +2416,11 @@ class TestAnalystDelete(MockEppLib):
|
||||||
Then a client error is returned of code 2305
|
Then a client error is returned of code 2305
|
||||||
And `state` is not set to `DELETED`
|
And `state` is not set to `DELETED`
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# Desired domain
|
# Desired domain
|
||||||
domain, _ = Domain.objects.get_or_create(name="failDelete.gov", state=Domain.State.ON_HOLD)
|
domain, _ = Domain.objects.get_or_create(name="failDelete.gov", state=Domain.State.ON_HOLD)
|
||||||
# Put the domain in client hold
|
# Put the domain in client hold
|
||||||
domain.place_client_hold()
|
domain.place_client_hold()
|
||||||
|
|
||||||
# Delete it
|
# Delete it
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
domain.deletedInEpp()
|
domain.deletedInEpp()
|
||||||
|
@ -2494,7 +2434,6 @@ class TestAnalystDelete(MockEppLib):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Domain itself should not be deleted
|
# Domain itself should not be deleted
|
||||||
self.assertNotEqual(domain, None)
|
self.assertNotEqual(domain, None)
|
||||||
# State should not have changed
|
# State should not have changed
|
||||||
|
@ -2511,6 +2450,7 @@ class TestAnalystDelete(MockEppLib):
|
||||||
|
|
||||||
The deleted date is still null.
|
The deleted date is still null.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||||
with self.assertRaises(TransitionNotAllowed) as err:
|
with self.assertRaises(TransitionNotAllowed) as err:
|
||||||
self.domain.deletedInEpp()
|
self.domain.deletedInEpp()
|
||||||
|
@ -2520,6 +2460,5 @@ class TestAnalystDelete(MockEppLib):
|
||||||
self.assertNotEqual(self.domain, None)
|
self.assertNotEqual(self.domain, None)
|
||||||
# Domain should have the right state
|
# Domain should have the right state
|
||||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||||
|
|
||||||
# deleted should be null
|
# deleted should be null
|
||||||
self.assertEqual(self.domain.deleted, None)
|
self.assertEqual(self.domain.deleted, None)
|
||||||
|
|
|
@ -7,13 +7,14 @@ from registrar.models.domain import Domain
|
||||||
from registrar.models.public_contact import PublicContact
|
from registrar.models.public_contact import PublicContact
|
||||||
from registrar.models.user import User
|
from registrar.models.user import User
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
from registrar.tests.common import MockEppLib
|
from registrar.tests.common import MockEppLib
|
||||||
from registrar.utility.csv_export import (
|
from registrar.utility.csv_export import (
|
||||||
write_header,
|
write_csv,
|
||||||
write_body,
|
|
||||||
get_default_start_date,
|
get_default_start_date,
|
||||||
get_default_end_date,
|
get_default_end_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from unittest.mock import MagicMock, call, mock_open, patch
|
from unittest.mock import MagicMock, call, mock_open, patch
|
||||||
from api.views import get_current_federal, get_current_full
|
from api.views import get_current_federal, get_current_full
|
||||||
|
@ -23,6 +24,7 @@ import boto3_mocking
|
||||||
from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore
|
from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from .common import less_console_noise
|
||||||
|
|
||||||
|
|
||||||
class CsvReportsTest(TestCase):
|
class CsvReportsTest(TestCase):
|
||||||
|
@ -80,6 +82,7 @@ class CsvReportsTest(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_generate_federal_report(self):
|
def test_generate_federal_report(self):
|
||||||
"""Ensures that we correctly generate current-federal.csv"""
|
"""Ensures that we correctly generate current-federal.csv"""
|
||||||
|
with less_console_noise():
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
fake_open = mock_open()
|
fake_open = mock_open()
|
||||||
expected_file_content = [
|
expected_file_content = [
|
||||||
|
@ -99,6 +102,7 @@ class CsvReportsTest(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_generate_full_report(self):
|
def test_generate_full_report(self):
|
||||||
"""Ensures that we correctly generate current-full.csv"""
|
"""Ensures that we correctly generate current-full.csv"""
|
||||||
|
with less_console_noise():
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
fake_open = mock_open()
|
fake_open = mock_open()
|
||||||
expected_file_content = [
|
expected_file_content = [
|
||||||
|
@ -123,6 +127,7 @@ class CsvReportsTest(TestCase):
|
||||||
def side_effect(Bucket, Key):
|
def side_effect(Bucket, Key):
|
||||||
raise ClientError({"Error": {"Code": "NoSuchKey", "Message": "No such key"}}, "get_object")
|
raise ClientError({"Error": {"Code": "NoSuchKey", "Message": "No such key"}}, "get_object")
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.get_object.side_effect = side_effect
|
mock_client.get_object.side_effect = side_effect
|
||||||
|
|
||||||
|
@ -144,6 +149,7 @@ class CsvReportsTest(TestCase):
|
||||||
def side_effect(Bucket, Key):
|
def side_effect(Bucket, Key):
|
||||||
raise ClientError({"Error": {"Code": "NoSuchKey", "Message": "No such key"}}, "get_object")
|
raise ClientError({"Error": {"Code": "NoSuchKey", "Message": "No such key"}}, "get_object")
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.get_object.side_effect = side_effect
|
mock_client.get_object.side_effect = side_effect
|
||||||
|
|
||||||
|
@ -160,6 +166,7 @@ class CsvReportsTest(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_load_federal_report(self):
|
def test_load_federal_report(self):
|
||||||
"""Tests the get_current_federal api endpoint"""
|
"""Tests the get_current_federal api endpoint"""
|
||||||
|
with less_console_noise():
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client_instance = mock_client.return_value
|
mock_client_instance = mock_client.return_value
|
||||||
|
|
||||||
|
@ -192,6 +199,7 @@ class CsvReportsTest(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_load_full_report(self):
|
def test_load_full_report(self):
|
||||||
"""Tests the current-federal api link"""
|
"""Tests the current-federal api link"""
|
||||||
|
with less_console_noise():
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client_instance = mock_client.return_value
|
mock_client_instance = mock_client.return_value
|
||||||
|
|
||||||
|
@ -329,34 +337,48 @@ class ExportDataTest(MockEppLib):
|
||||||
federal_agency="Armed Forces Retirement Home",
|
federal_agency="Armed Forces Retirement Home",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
meoward_user = get_user_model().objects.create(
|
||||||
|
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test for more than 1 domain manager
|
||||||
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
user=meoward_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test for just 1 domain manager
|
||||||
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
PublicContact.objects.all().delete()
|
PublicContact.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
UserDomainRole.objects.all().delete()
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
def test_export_domains_to_writer_security_emails(self):
|
def test_export_domains_to_writer_security_emails(self):
|
||||||
"""Test that export_domains_to_writer returns the
|
"""Test that export_domains_to_writer returns the
|
||||||
expected security email"""
|
expected security email"""
|
||||||
|
with less_console_noise():
|
||||||
# Add security email information
|
# Add security email information
|
||||||
self.domain_1.name = "defaultsecurity.gov"
|
self.domain_1.name = "defaultsecurity.gov"
|
||||||
self.domain_1.save()
|
self.domain_1.save()
|
||||||
|
|
||||||
# Invoke setter
|
# Invoke setter
|
||||||
self.domain_1.security_contact
|
self.domain_1.security_contact
|
||||||
|
|
||||||
# Invoke setter
|
# Invoke setter
|
||||||
self.domain_2.security_contact
|
self.domain_2.security_contact
|
||||||
|
|
||||||
# Invoke setter
|
# Invoke setter
|
||||||
self.domain_3.security_contact
|
self.domain_3.security_contact
|
||||||
|
|
||||||
# Create a CSV file in memory
|
# Create a CSV file in memory
|
||||||
csv_file = StringIO()
|
csv_file = StringIO()
|
||||||
writer = csv.writer(csv_file)
|
writer = csv.writer(csv_file)
|
||||||
|
|
||||||
# Define columns, sort fields, and filter condition
|
# Define columns, sort fields, and filter condition
|
||||||
columns = [
|
columns = [
|
||||||
"Domain name",
|
"Domain name",
|
||||||
|
@ -379,18 +401,16 @@ class ExportDataTest(MockEppLib):
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
# Call the export functions
|
# Call the export functions
|
||||||
write_header(writer, columns)
|
write_csv(
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True
|
||||||
|
)
|
||||||
|
|
||||||
# Reset the CSV file's position to the beginning
|
# Reset the CSV file's position to the beginning
|
||||||
csv_file.seek(0)
|
csv_file.seek(0)
|
||||||
|
|
||||||
# Read the content into a variable
|
# Read the content into a variable
|
||||||
csv_content = csv_file.read()
|
csv_content = csv_file.read()
|
||||||
|
|
||||||
# We expect READY domains,
|
# We expect READY domains,
|
||||||
# sorted alphabetially by domain name
|
# sorted alphabetially by domain name
|
||||||
expected_content = (
|
expected_content = (
|
||||||
|
@ -401,18 +421,17 @@ class ExportDataTest(MockEppLib):
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\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"
|
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||||
|
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
||||||
def test_write_body(self):
|
def test_write_csv(self):
|
||||||
"""Test that write_body returns the
|
"""Test that write_body returns the
|
||||||
existing domain, test that sort by domain name works,
|
existing domain, test that sort by domain name works,
|
||||||
test that filter works"""
|
test that filter works"""
|
||||||
|
with less_console_noise():
|
||||||
# Create a CSV file in memory
|
# Create a CSV file in memory
|
||||||
csv_file = StringIO()
|
csv_file = StringIO()
|
||||||
writer = csv.writer(csv_file)
|
writer = csv.writer(csv_file)
|
||||||
|
@ -442,17 +461,14 @@ class ExportDataTest(MockEppLib):
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Call the export functions
|
# Call the export functions
|
||||||
write_header(writer, columns)
|
write_csv(
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True
|
||||||
|
)
|
||||||
# Reset the CSV file's position to the beginning
|
# Reset the CSV file's position to the beginning
|
||||||
csv_file.seek(0)
|
csv_file.seek(0)
|
||||||
|
|
||||||
# Read the content into a variable
|
# Read the content into a variable
|
||||||
csv_content = csv_file.read()
|
csv_content = csv_file.read()
|
||||||
|
|
||||||
# We expect READY domains,
|
# We expect READY domains,
|
||||||
# sorted alphabetially by domain name
|
# sorted alphabetially by domain name
|
||||||
expected_content = (
|
expected_content = (
|
||||||
|
@ -464,20 +480,18 @@ class ExportDataTest(MockEppLib):
|
||||||
"cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n"
|
"cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||||
|
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
||||||
def test_write_body_additional(self):
|
def test_write_body_additional(self):
|
||||||
"""An additional test for filters and multi-column sort"""
|
"""An additional test for filters and multi-column sort"""
|
||||||
|
with less_console_noise():
|
||||||
# Create a CSV file in memory
|
# Create a CSV file in memory
|
||||||
csv_file = StringIO()
|
csv_file = StringIO()
|
||||||
writer = csv.writer(csv_file)
|
writer = csv.writer(csv_file)
|
||||||
|
|
||||||
# Define columns, sort fields, and filter condition
|
# Define columns, sort fields, and filter condition
|
||||||
columns = [
|
columns = [
|
||||||
"Domain name",
|
"Domain name",
|
||||||
|
@ -497,17 +511,14 @@ class ExportDataTest(MockEppLib):
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Call the export functions
|
# Call the export functions
|
||||||
write_header(writer, columns)
|
write_csv(
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True
|
||||||
|
)
|
||||||
# Reset the CSV file's position to the beginning
|
# Reset the CSV file's position to the beginning
|
||||||
csv_file.seek(0)
|
csv_file.seek(0)
|
||||||
|
|
||||||
# Read the content into a variable
|
# Read the content into a variable
|
||||||
csv_content = csv_file.read()
|
csv_content = csv_file.read()
|
||||||
|
|
||||||
# We expect READY domains,
|
# We expect READY domains,
|
||||||
# federal only
|
# federal only
|
||||||
# sorted alphabetially by domain name
|
# sorted alphabetially by domain name
|
||||||
|
@ -518,12 +529,10 @@ class ExportDataTest(MockEppLib):
|
||||||
"cdomain1.gov,Federal - Executive,World War I Centennial Commission\n"
|
"cdomain1.gov,Federal - Executive,World War I Centennial Commission\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||||
|
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
||||||
def test_write_body_with_date_filter_pulls_domains_in_range(self):
|
def test_write_body_with_date_filter_pulls_domains_in_range(self):
|
||||||
|
@ -538,12 +547,12 @@ class ExportDataTest(MockEppLib):
|
||||||
which are hard to mock.
|
which are hard to mock.
|
||||||
|
|
||||||
TODO: Simplify is created_at is not needed for the report."""
|
TODO: Simplify is created_at is not needed for the report."""
|
||||||
|
with less_console_noise():
|
||||||
# Create a CSV file in memory
|
# Create a CSV file in memory
|
||||||
csv_file = StringIO()
|
csv_file = StringIO()
|
||||||
writer = csv.writer(csv_file)
|
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())
|
# We use timezone.make_aware to sync to server time a datetime object with the current date
|
||||||
# and a specific time (using datetime.min.time()).
|
# (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()))
|
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()))
|
start_date = timezone.make_aware(datetime.combine(date.today() - timedelta(days=2), datetime.min.time()))
|
||||||
|
|
||||||
|
@ -582,20 +591,22 @@ class ExportDataTest(MockEppLib):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Call the export functions
|
# Call the export functions
|
||||||
write_header(writer, columns)
|
write_csv(
|
||||||
write_body(
|
|
||||||
writer,
|
writer,
|
||||||
columns,
|
columns,
|
||||||
sort_fields,
|
sort_fields,
|
||||||
filter_condition,
|
filter_condition,
|
||||||
|
get_domain_managers=False,
|
||||||
|
should_write_header=True,
|
||||||
)
|
)
|
||||||
write_body(
|
write_csv(
|
||||||
writer,
|
writer,
|
||||||
columns,
|
columns,
|
||||||
sort_fields_for_deleted_domains,
|
sort_fields_for_deleted_domains,
|
||||||
filter_conditions_for_deleted_domains,
|
filter_conditions_for_deleted_domains,
|
||||||
|
get_domain_managers=False,
|
||||||
|
should_write_header=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset the CSV file's position to the beginning
|
# Reset the CSV file's position to the beginning
|
||||||
csv_file.seek(0)
|
csv_file.seek(0)
|
||||||
|
|
||||||
|
@ -621,6 +632,64 @@ class ExportDataTest(MockEppLib):
|
||||||
|
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
||||||
|
def test_export_domains_to_writer_domain_managers(self):
|
||||||
|
"""Test that export_domains_to_writer returns the
|
||||||
|
expected domain managers"""
|
||||||
|
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",
|
||||||
|
"Status",
|
||||||
|
"Expiration date",
|
||||||
|
"Domain type",
|
||||||
|
"Agency",
|
||||||
|
"Organization name",
|
||||||
|
"City",
|
||||||
|
"State",
|
||||||
|
"AO",
|
||||||
|
"AO email",
|
||||||
|
"Security contact email",
|
||||||
|
]
|
||||||
|
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_csv(
|
||||||
|
writer, columns, sort_fields, filter_condition, get_domain_managers=True, should_write_header=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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,Status,Expiration date,Domain type,Agency,"
|
||||||
|
"Organization name,City,State,AO,AO email,"
|
||||||
|
"Security contact email,Domain manager email 1,Domain manager email 2,\n"
|
||||||
|
"adomain10.gov,Ready,,Federal,Armed Forces Retirement Home,,,, , ,\n"
|
||||||
|
"adomain2.gov,Dns needed,,Interstate,,,,, , , ,meoward@rocks.com\n"
|
||||||
|
"cdomain1.gov,Ready,,Federal - Executive,World War I Centennial Commission,,,"
|
||||||
|
", , , ,meoward@rocks.com,info@example.com\n"
|
||||||
|
"ddomain3.gov,On hold,,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)
|
||||||
|
|
||||||
|
|
||||||
class HelperFunctions(TestCase):
|
class HelperFunctions(TestCase):
|
||||||
"""This asserts that 1=1. Its limited usefulness lies in making sure the helper methods stay healthy."""
|
"""This asserts that 1=1. Its limited usefulness lies in making sure the helper methods stay healthy."""
|
||||||
|
|
|
@ -20,6 +20,9 @@ from registrar.models.contact import Contact
|
||||||
|
|
||||||
from .common import MockSESClient, less_console_noise
|
from .common import MockSESClient, less_console_noise
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestProcessedMigrations(TestCase):
|
class TestProcessedMigrations(TestCase):
|
||||||
|
@ -55,6 +58,7 @@ class TestProcessedMigrations(TestCase):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the load_transition_domain command with the specified arguments.
|
execute the load_transition_domain command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# noqa here because splitting this up makes it confusing.
|
# noqa here because splitting this up makes it confusing.
|
||||||
# ES501
|
# ES501
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -74,6 +78,7 @@ class TestProcessedMigrations(TestCase):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the load_transition_domain command with the specified arguments.
|
execute the load_transition_domain command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
call_command("transfer_transition_domains_to_domains")
|
call_command("transfer_transition_domains_to_domains")
|
||||||
|
|
||||||
def test_domain_idempotent(self):
|
def test_domain_idempotent(self):
|
||||||
|
@ -81,6 +86,7 @@ class TestProcessedMigrations(TestCase):
|
||||||
This test ensures that the domain transfer process
|
This test ensures that the domain transfer process
|
||||||
is idempotent on Domain and DomainInformation.
|
is idempotent on Domain and DomainInformation.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
unchanged_domain, _ = Domain.objects.get_or_create(
|
unchanged_domain, _ = Domain.objects.get_or_create(
|
||||||
name="testdomain.gov",
|
name="testdomain.gov",
|
||||||
state=Domain.State.READY,
|
state=Domain.State.READY,
|
||||||
|
@ -139,6 +145,7 @@ class TestProcessedMigrations(TestCase):
|
||||||
"""
|
"""
|
||||||
This test checks if a domain is correctly marked as processed in the transition.
|
This test checks if a domain is correctly marked as processed in the transition.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
old_transition_domain, _ = TransitionDomain.objects.get_or_create(domain_name="testdomain.gov")
|
old_transition_domain, _ = TransitionDomain.objects.get_or_create(domain_name="testdomain.gov")
|
||||||
# Asser that old records default to 'True'
|
# Asser that old records default to 'True'
|
||||||
self.assertTrue(old_transition_domain.processed)
|
self.assertTrue(old_transition_domain.processed)
|
||||||
|
@ -200,6 +207,7 @@ class TestOrganizationMigration(TestCase):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the load_transition_domain command with the specified arguments.
|
execute the load_transition_domain command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# noqa here because splitting this up makes it confusing.
|
# noqa here because splitting this up makes it confusing.
|
||||||
# ES501
|
# ES501
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -219,6 +227,7 @@ class TestOrganizationMigration(TestCase):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the load_transition_domain command with the specified arguments.
|
execute the load_transition_domain command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
call_command("transfer_transition_domains_to_domains")
|
call_command("transfer_transition_domains_to_domains")
|
||||||
|
|
||||||
def run_load_organization_data(self):
|
def run_load_organization_data(self):
|
||||||
|
@ -232,6 +241,7 @@ class TestOrganizationMigration(TestCase):
|
||||||
The 'call_command' function from Django's management framework is then used to
|
The 'call_command' function from Django's management framework is then used to
|
||||||
execute the load_organization_data command with the specified arguments.
|
execute the load_organization_data command with the specified arguments.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# noqa here (E501) because splitting this up makes it
|
# noqa here (E501) because splitting this up makes it
|
||||||
# confusing to read.
|
# confusing to read.
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -256,7 +266,6 @@ class TestOrganizationMigration(TestCase):
|
||||||
"""Does a diff between the transition_domain and the following tables:
|
"""Does a diff between the transition_domain and the following tables:
|
||||||
domain, domain_information and the domain_invitation.
|
domain, domain_information and the domain_invitation.
|
||||||
Verifies that the data loaded correctly."""
|
Verifies that the data loaded correctly."""
|
||||||
|
|
||||||
missing_domains = []
|
missing_domains = []
|
||||||
duplicate_domains = []
|
duplicate_domains = []
|
||||||
missing_domain_informations = []
|
missing_domain_informations = []
|
||||||
|
@ -300,8 +309,11 @@ class TestOrganizationMigration(TestCase):
|
||||||
3. Checks that the data has been loaded as expected.
|
3. Checks that the data has been loaded as expected.
|
||||||
|
|
||||||
The expected result is a set of TransitionDomain objects with specific attributes.
|
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.
|
The test fetches the actual TransitionDomain objects from the database and compares them with
|
||||||
""" # noqa - E501 (harder to read)
|
the expected objects.
|
||||||
|
"""
|
||||||
|
with less_console_noise():
|
||||||
|
# noqa - E501 (harder to read)
|
||||||
# == First, parse all existing data == #
|
# == First, parse all existing data == #
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
@ -346,7 +358,9 @@ class TestOrganizationMigration(TestCase):
|
||||||
def test_transition_domain_status_unknown(self):
|
def test_transition_domain_status_unknown(self):
|
||||||
"""
|
"""
|
||||||
Test that a domain in unknown status can be loaded
|
Test that a domain in unknown status can be loaded
|
||||||
""" # noqa - E501 (harder to read)
|
"""
|
||||||
|
with less_console_noise():
|
||||||
|
# noqa - E501 (harder to read)
|
||||||
# == First, parse all existing data == #
|
# == First, parse all existing data == #
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
@ -367,6 +381,7 @@ class TestOrganizationMigration(TestCase):
|
||||||
The test fetches the actual DomainInformation object from the database
|
The test fetches the actual DomainInformation object from the database
|
||||||
and compares it with the expected object.
|
and compares it with the expected object.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# == First, parse all existing data == #
|
# == First, parse all existing data == #
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
@ -379,7 +394,9 @@ class TestOrganizationMigration(TestCase):
|
||||||
domain_information = DomainInformation.objects.filter(domain=_domain).get()
|
domain_information = DomainInformation.objects.filter(domain=_domain).get()
|
||||||
|
|
||||||
expected_creator = User.objects.filter(username="System").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_ao = Contact.objects.filter(
|
||||||
|
first_name="Seline", middle_name="testmiddle2", last_name="Tower"
|
||||||
|
).get()
|
||||||
expected_domain_information = DomainInformation(
|
expected_domain_information = DomainInformation(
|
||||||
creator=expected_creator,
|
creator=expected_creator,
|
||||||
organization_type="federal",
|
organization_type="federal",
|
||||||
|
@ -410,6 +427,7 @@ class TestOrganizationMigration(TestCase):
|
||||||
The expected result is that the DomainInformation object retains its pre-existing data
|
The expected result is that the DomainInformation object retains its pre-existing data
|
||||||
after the load_organization_data method is run.
|
after the load_organization_data method is run.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# == First, parse all existing data == #
|
# == First, parse all existing data == #
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
@ -431,7 +449,9 @@ class TestOrganizationMigration(TestCase):
|
||||||
domain_information = DomainInformation.objects.filter(domain=_domain).get()
|
domain_information = DomainInformation.objects.filter(domain=_domain).get()
|
||||||
|
|
||||||
expected_creator = User.objects.filter(username="System").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_ao = Contact.objects.filter(
|
||||||
|
first_name="Seline", middle_name="testmiddle2", last_name="Tower"
|
||||||
|
).get()
|
||||||
expected_domain_information = DomainInformation(
|
expected_domain_information = DomainInformation(
|
||||||
creator=expected_creator,
|
creator=expected_creator,
|
||||||
organization_type="federal",
|
organization_type="federal",
|
||||||
|
@ -462,6 +482,7 @@ class TestOrganizationMigration(TestCase):
|
||||||
The expected result is that the counts of objects in the database
|
The expected result is that the counts of objects in the database
|
||||||
match the expected counts, indicating that the data has not been corrupted.
|
match the expected counts, indicating that the data has not been corrupted.
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
# First, parse all existing data
|
# First, parse all existing data
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
@ -521,6 +542,7 @@ class TestMigrations(TestCase):
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
|
||||||
def run_load_domains(self):
|
def run_load_domains(self):
|
||||||
|
with less_console_noise():
|
||||||
# noqa here because splitting this up makes it confusing.
|
# noqa here because splitting this up makes it confusing.
|
||||||
# ES501
|
# ES501
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -534,9 +556,11 @@ class TestMigrations(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_transfer_domains(self):
|
def run_transfer_domains(self):
|
||||||
|
with less_console_noise():
|
||||||
call_command("transfer_transition_domains_to_domains")
|
call_command("transfer_transition_domains_to_domains")
|
||||||
|
|
||||||
def run_master_script(self):
|
def run_master_script(self):
|
||||||
|
with less_console_noise():
|
||||||
# noqa here (E501) because splitting this up makes it
|
# noqa here (E501) because splitting this up makes it
|
||||||
# confusing to read.
|
# confusing to read.
|
||||||
mock_client = MockSESClient()
|
mock_client = MockSESClient()
|
||||||
|
@ -553,7 +577,7 @@ class TestMigrations(TestCase):
|
||||||
migrationJSON=self.migration_json_filename,
|
migrationJSON=self.migration_json_filename,
|
||||||
disablePrompts=True,
|
disablePrompts=True,
|
||||||
)
|
)
|
||||||
print(f"here: {mock_client.EMAILS_SENT}")
|
logger.debug(f"here: {mock_client.EMAILS_SENT}")
|
||||||
|
|
||||||
def compare_tables(
|
def compare_tables(
|
||||||
self,
|
self,
|
||||||
|
@ -607,7 +631,7 @@ class TestMigrations(TestCase):
|
||||||
total_domain_informations = len(DomainInformation.objects.all())
|
total_domain_informations = len(DomainInformation.objects.all())
|
||||||
total_domain_invitations = len(DomainInvitation.objects.all())
|
total_domain_invitations = len(DomainInvitation.objects.all())
|
||||||
|
|
||||||
print(
|
logger.debug(
|
||||||
f"""
|
f"""
|
||||||
total_missing_domains = {len(missing_domains)}
|
total_missing_domains = {len(missing_domains)}
|
||||||
total_duplicate_domains = {len(duplicate_domains)}
|
total_duplicate_domains = {len(duplicate_domains)}
|
||||||
|
@ -636,7 +660,7 @@ class TestMigrations(TestCase):
|
||||||
follow best practice of limiting the number of assertions per test.
|
follow best practice of limiting the number of assertions per test.
|
||||||
But for now, this will double-check that the script
|
But for now, this will double-check that the script
|
||||||
works as intended."""
|
works as intended."""
|
||||||
|
with less_console_noise():
|
||||||
self.run_master_script()
|
self.run_master_script()
|
||||||
|
|
||||||
# STEP 2: (analyze the tables just like the
|
# STEP 2: (analyze the tables just like the
|
||||||
|
@ -664,6 +688,7 @@ class TestMigrations(TestCase):
|
||||||
|
|
||||||
def test_load_empty_transition_domain(self):
|
def test_load_empty_transition_domain(self):
|
||||||
"""Loads TransitionDomains without additional data"""
|
"""Loads TransitionDomains without additional data"""
|
||||||
|
with less_console_noise():
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
|
|
||||||
# STEP 2: (analyze the tables just like the migration
|
# STEP 2: (analyze the tables just like the migration
|
||||||
|
@ -689,6 +714,7 @@ class TestMigrations(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_load_full_domain(self):
|
def test_load_full_domain(self):
|
||||||
|
with less_console_noise():
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
|
||||||
|
@ -733,6 +759,7 @@ class TestMigrations(TestCase):
|
||||||
self.assertEqual(testdomain.state, "on hold")
|
self.assertEqual(testdomain.state, "on hold")
|
||||||
|
|
||||||
def test_load_full_domain_information(self):
|
def test_load_full_domain_information(self):
|
||||||
|
with less_console_noise():
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
|
||||||
|
@ -800,6 +827,7 @@ class TestMigrations(TestCase):
|
||||||
self.assertEqual(anomaly.creator, Users.get())
|
self.assertEqual(anomaly.creator, Users.get())
|
||||||
|
|
||||||
def test_transfer_transition_domains_to_domains(self):
|
def test_transfer_transition_domains_to_domains(self):
|
||||||
|
with less_console_noise():
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
|
||||||
|
@ -825,6 +853,7 @@ class TestMigrations(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_logins(self):
|
def test_logins(self):
|
||||||
|
with less_console_noise():
|
||||||
# TODO: setup manually instead of calling other scripts
|
# TODO: setup manually instead of calling other scripts
|
||||||
self.run_load_domains()
|
self.run_load_domains()
|
||||||
self.run_transfer_domains()
|
self.run_transfer_domains()
|
||||||
|
|
|
@ -114,6 +114,13 @@ class TestURLAuth(TestCase):
|
||||||
"/api/v1/available/",
|
"/api/v1/available/",
|
||||||
"/api/v1/get-report/current-federal",
|
"/api/v1/get-report/current-federal",
|
||||||
"/api/v1/get-report/current-full",
|
"/api/v1/get-report/current-full",
|
||||||
|
"/health",
|
||||||
|
]
|
||||||
|
|
||||||
|
# We will test that the following URLs are not protected by auth
|
||||||
|
# and that the url returns a 200 response
|
||||||
|
NO_AUTH_URLS = [
|
||||||
|
"/health",
|
||||||
]
|
]
|
||||||
|
|
||||||
def assertURLIsProtectedByAuth(self, url):
|
def assertURLIsProtectedByAuth(self, url):
|
||||||
|
@ -147,9 +154,33 @@ class TestURLAuth(TestCase):
|
||||||
f"GET {url} returned HTTP {code}, but should redirect to login or deny access",
|
f"GET {url} returned HTTP {code}, but should redirect to login or deny access",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def assertURLIsNotProtectedByAuth(self, url):
|
||||||
|
"""
|
||||||
|
Make a GET request to the given URL, and ensure that it returns 200.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
with less_console_noise():
|
||||||
|
response = self.client.get(url)
|
||||||
|
except Exception as e:
|
||||||
|
# It'll be helpful to provide information on what URL was being
|
||||||
|
# accessed at the time the exception occurred. Python 3 will
|
||||||
|
# also include a full traceback of the original exception, so
|
||||||
|
# we don't need to worry about hiding the original cause.
|
||||||
|
raise AssertionError(f'Accessing {url} raised "{e}"', e)
|
||||||
|
|
||||||
|
code = response.status_code
|
||||||
|
if code != 200:
|
||||||
|
raise AssertionError(
|
||||||
|
f"GET {url} returned HTTP {code}, but should return 200 OK",
|
||||||
|
)
|
||||||
|
|
||||||
def test_login_required_all_urls(self):
|
def test_login_required_all_urls(self):
|
||||||
"""All URLs redirect to the login view."""
|
"""All URLs redirect to the login view."""
|
||||||
for viewname, url in iter_sample_urls(registrar.config.urls):
|
for viewname, url in iter_sample_urls(registrar.config.urls):
|
||||||
if url not in self.IGNORE_URLS:
|
if url not in self.IGNORE_URLS:
|
||||||
with self.subTest(viewname=viewname):
|
with self.subTest(viewname=viewname):
|
||||||
self.assertURLIsProtectedByAuth(url)
|
self.assertURLIsProtectedByAuth(url)
|
||||||
|
elif url in self.NO_AUTH_URLS:
|
||||||
|
with self.subTest(viewname=viewname):
|
||||||
|
self.assertURLIsNotProtectedByAuth(url)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from .common import MockEppLib # type: ignore
|
from .common import MockEppLib # type: ignore
|
||||||
|
@ -8,11 +7,7 @@ from .common import MockEppLib # type: ignore
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
DomainApplication,
|
DomainApplication,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
DraftDomain,
|
|
||||||
Contact,
|
|
||||||
User,
|
|
||||||
)
|
)
|
||||||
from .common import less_console_noise
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -23,7 +18,7 @@ class TestViews(TestCase):
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
|
|
||||||
def test_health_check_endpoint(self):
|
def test_health_check_endpoint(self):
|
||||||
response = self.client.get("/health/")
|
response = self.client.get("/health")
|
||||||
self.assertContains(response, "OK", status_code=200)
|
self.assertContains(response, "OK", status_code=200)
|
||||||
|
|
||||||
def test_home_page(self):
|
def test_home_page(self):
|
||||||
|
@ -55,252 +50,3 @@ class TestWithUser(MockEppLib):
|
||||||
DomainApplication.objects.all().delete()
|
DomainApplication.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
self.user.delete()
|
self.user.delete()
|
||||||
|
|
||||||
|
|
||||||
class LoggedInTests(TestWithUser):
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super().tearDown()
|
|
||||||
Contact.objects.all().delete()
|
|
||||||
|
|
||||||
def test_home_lists_domain_applications(self):
|
|
||||||
response = self.client.get("/")
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(creator=self.user, requested_domain=site)
|
|
||||||
response = self.client.get("/")
|
|
||||||
|
|
||||||
# count = 7 because of screenreader content
|
|
||||||
self.assertContains(response, "igorville.gov", count=7)
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_deletes_withdrawn_domain_application(self):
|
|
||||||
"""Tests if the user can delete a DomainApplication in the 'withdrawn' status"""
|
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that igorville.gov exists on the page
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "igorville.gov")
|
|
||||||
|
|
||||||
# Check if the delete button exists. We can do this by checking for its id and text content.
|
|
||||||
self.assertContains(home_page, "Delete")
|
|
||||||
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
|
||||||
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_deletes_started_domain_application(self):
|
|
||||||
"""Tests if the user can delete a DomainApplication in the 'started' status"""
|
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.STARTED
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that igorville.gov exists on the page
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "igorville.gov")
|
|
||||||
|
|
||||||
# Check if the delete button exists. We can do this by checking for its id and text content.
|
|
||||||
self.assertContains(home_page, "Delete")
|
|
||||||
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
|
||||||
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_doesnt_delete_other_domain_applications(self):
|
|
||||||
"""Tests to ensure the user can't delete Applications not in the status of STARTED or WITHDRAWN"""
|
|
||||||
|
|
||||||
# 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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
desired_application = DomainApplication.objects.filter(requested_domain=draft_domain)
|
|
||||||
|
|
||||||
# Make sure the DomainApplication wasn't deleted
|
|
||||||
self.assertEqual(desired_application.count(), 1)
|
|
||||||
|
|
||||||
# clean up
|
|
||||||
application.delete()
|
|
||||||
|
|
||||||
def test_home_deletes_domain_application_and_orphans(self):
|
|
||||||
"""Tests if delete for DomainApplication deletes orphaned Contact objects"""
|
|
||||||
|
|
||||||
# Create the site and contacts to delete (orphaned)
|
|
||||||
contact = Contact.objects.create(
|
|
||||||
first_name="Henry",
|
|
||||||
last_name="Mcfakerson",
|
|
||||||
)
|
|
||||||
contact_shared = Contact.objects.create(
|
|
||||||
first_name="Relative",
|
|
||||||
last_name="Aether",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create two non-orphaned contacts
|
|
||||||
contact_2 = Contact.objects.create(
|
|
||||||
first_name="Saturn",
|
|
||||||
last_name="Mars",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attach a user object to a contact (should not be deleted)
|
|
||||||
contact_user, _ = Contact.objects.get_or_create(user=self.user)
|
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user,
|
|
||||||
requested_domain=site,
|
|
||||||
status=DomainApplication.ApplicationStatus.WITHDRAWN,
|
|
||||||
authorizing_official=contact,
|
|
||||||
submitter=contact_user,
|
|
||||||
)
|
|
||||||
application.other_contacts.set([contact_2])
|
|
||||||
|
|
||||||
# Create a second application to attach contacts to
|
|
||||||
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
|
||||||
application_2 = DomainApplication.objects.create(
|
|
||||||
creator=self.user,
|
|
||||||
requested_domain=site_2,
|
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
|
||||||
authorizing_official=contact_2,
|
|
||||||
submitter=contact_shared,
|
|
||||||
)
|
|
||||||
application_2.other_contacts.set([contact_shared])
|
|
||||||
|
|
||||||
# Ensure that igorville.gov exists on the page
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "igorville.gov")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
|
||||||
|
|
||||||
# igorville is now deleted
|
|
||||||
self.assertNotContains(response, "igorville.gov")
|
|
||||||
|
|
||||||
# Check if the orphaned contact was deleted
|
|
||||||
orphan = Contact.objects.filter(id=contact.id)
|
|
||||||
self.assertFalse(orphan.exists())
|
|
||||||
|
|
||||||
# All non-orphan contacts should still exist and are unaltered
|
|
||||||
try:
|
|
||||||
current_user = Contact.objects.filter(id=contact_user.id).get()
|
|
||||||
except Contact.DoesNotExist:
|
|
||||||
self.fail("contact_user (a non-orphaned contact) was deleted")
|
|
||||||
|
|
||||||
self.assertEqual(current_user, contact_user)
|
|
||||||
try:
|
|
||||||
edge_case = Contact.objects.filter(id=contact_2.id).get()
|
|
||||||
except Contact.DoesNotExist:
|
|
||||||
self.fail("contact_2 (a non-orphaned contact) was deleted")
|
|
||||||
|
|
||||||
self.assertEqual(edge_case, contact_2)
|
|
||||||
|
|
||||||
def test_home_deletes_domain_application_and_shared_orphans(self):
|
|
||||||
"""Test the edge case for an object that will become orphaned after a delete
|
|
||||||
(but is not an orphan at the time of deletion)"""
|
|
||||||
|
|
||||||
# Create the site and contacts to delete (orphaned)
|
|
||||||
contact = Contact.objects.create(
|
|
||||||
first_name="Henry",
|
|
||||||
last_name="Mcfakerson",
|
|
||||||
)
|
|
||||||
contact_shared = Contact.objects.create(
|
|
||||||
first_name="Relative",
|
|
||||||
last_name="Aether",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create two non-orphaned contacts
|
|
||||||
contact_2 = Contact.objects.create(
|
|
||||||
first_name="Saturn",
|
|
||||||
last_name="Mars",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attach a user object to a contact (should not be deleted)
|
|
||||||
contact_user, _ = Contact.objects.get_or_create(user=self.user)
|
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
|
||||||
application = DomainApplication.objects.create(
|
|
||||||
creator=self.user,
|
|
||||||
requested_domain=site,
|
|
||||||
status=DomainApplication.ApplicationStatus.WITHDRAWN,
|
|
||||||
authorizing_official=contact,
|
|
||||||
submitter=contact_user,
|
|
||||||
)
|
|
||||||
application.other_contacts.set([contact_2])
|
|
||||||
|
|
||||||
# Create a second application to attach contacts to
|
|
||||||
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
|
||||||
application_2 = DomainApplication.objects.create(
|
|
||||||
creator=self.user,
|
|
||||||
requested_domain=site_2,
|
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
|
||||||
authorizing_official=contact_2,
|
|
||||||
submitter=contact_shared,
|
|
||||||
)
|
|
||||||
application_2.other_contacts.set([contact_shared])
|
|
||||||
|
|
||||||
home_page = self.client.get("/")
|
|
||||||
self.assertContains(home_page, "teaville.gov")
|
|
||||||
|
|
||||||
# Trigger the delete logic
|
|
||||||
response = self.client.post(reverse("application-delete", kwargs={"pk": application_2.pk}), follow=True)
|
|
||||||
|
|
||||||
self.assertNotContains(response, "teaville.gov")
|
|
||||||
|
|
||||||
# Check if the orphaned contact was deleted
|
|
||||||
orphan = Contact.objects.filter(id=contact_shared.id)
|
|
||||||
self.assertFalse(orphan.exists())
|
|
||||||
|
|
||||||
def test_application_form_view(self):
|
|
||||||
response = self.client.get("/request/", follow=True)
|
|
||||||
self.assertContains(
|
|
||||||
response,
|
|
||||||
"You’re about to start your .gov domain request.",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_domain_application_form_with_ineligible_user(self):
|
|
||||||
"""Application form not accessible for an ineligible user.
|
|
||||||
This test should be solid enough since all application wizard
|
|
||||||
views share the same permissions class"""
|
|
||||||
self.user.status = User.RESTRICTED
|
|
||||||
self.user.save()
|
|
||||||
|
|
||||||
with less_console_noise():
|
|
||||||
response = self.client.get("/request/", follow=True)
|
|
||||||
print(response.status_code)
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from unittest import skip
|
from unittest import skip
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from .common import MockSESClient, completed_application # type: ignore
|
from .common import MockSESClient, completed_application # type: ignore
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
|
@ -9,11 +11,13 @@ import boto3_mocking # type: ignore
|
||||||
|
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
DomainApplication,
|
DomainApplication,
|
||||||
|
DraftDomain,
|
||||||
Domain,
|
Domain,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
Contact,
|
Contact,
|
||||||
User,
|
User,
|
||||||
Website,
|
Website,
|
||||||
|
UserDomainRole,
|
||||||
)
|
)
|
||||||
from registrar.views.application import ApplicationWizard, Step
|
from registrar.views.application import ApplicationWizard, Step
|
||||||
|
|
||||||
|
@ -26,7 +30,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationTests(TestWithUser, WebTest):
|
class DomainApplicationTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
"""Webtests for domain application to test filling and submitting."""
|
"""Webtests for domain application to test filling and submitting."""
|
||||||
|
|
||||||
# Doesn't work with CSRF checking
|
# Doesn't work with CSRF checking
|
||||||
|
@ -2197,3 +2200,492 @@ class DomainApplicationTestDifferentStatuses(TestWithUser, WebTest):
|
||||||
# domain object, so we do not expect to see 'city.gov'
|
# domain object, so we do not expect to see 'city.gov'
|
||||||
# in either the Domains or Requests tables.
|
# in either the Domains or Requests tables.
|
||||||
self.assertNotContains(home_page, "city.gov")
|
self.assertNotContains(home_page, "city.gov")
|
||||||
|
|
||||||
|
|
||||||
|
class TestWizardUnlockingSteps(TestWithUser, WebTest):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
self.wizard = ApplicationWizard()
|
||||||
|
# Mock the request object, its user, and session attributes appropriately
|
||||||
|
self.wizard.request = Mock(user=self.user, session={})
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
def test_unlocked_steps_empty_application(self):
|
||||||
|
"""Test when all fields in the application are empty."""
|
||||||
|
unlocked_steps = self.wizard.db_check_for_unlocking_steps()
|
||||||
|
expected_dict = []
|
||||||
|
self.assertEqual(unlocked_steps, expected_dict)
|
||||||
|
|
||||||
|
def test_unlocked_steps_full_application(self):
|
||||||
|
"""Test when all fields in the application are filled."""
|
||||||
|
|
||||||
|
completed_application(status=DomainApplication.ApplicationStatus.STARTED, user=self.user)
|
||||||
|
# Make a request to the home page
|
||||||
|
home_page = self.app.get("/")
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Assert that the response contains "city.gov"
|
||||||
|
self.assertContains(home_page, "city.gov")
|
||||||
|
|
||||||
|
# Click the "Edit" link
|
||||||
|
response = home_page.click("Edit", index=0)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
# Check if the response is a redirect
|
||||||
|
if response.status_code == 302:
|
||||||
|
# Follow the redirect manually
|
||||||
|
try:
|
||||||
|
detail_page = response.follow()
|
||||||
|
|
||||||
|
self.wizard.get_context_data()
|
||||||
|
except Exception as err:
|
||||||
|
# Handle any potential errors while following the redirect
|
||||||
|
self.fail(f"Error following the redirect {err}")
|
||||||
|
|
||||||
|
# Now 'detail_page' contains the response after following the redirect
|
||||||
|
self.assertEqual(detail_page.status_code, 200)
|
||||||
|
|
||||||
|
# 10 unlocked steps, one active step, the review step will have link_usa but not check_circle
|
||||||
|
self.assertContains(detail_page, "#check_circle", count=10)
|
||||||
|
# Type of organization
|
||||||
|
self.assertContains(detail_page, "usa-current", count=1)
|
||||||
|
self.assertContains(detail_page, "link_usa-checked", count=11)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.fail(f"Expected a redirect, but got a different response: {response}")
|
||||||
|
|
||||||
|
def test_unlocked_steps_partial_application(self):
|
||||||
|
"""Test when some fields in the application are filled."""
|
||||||
|
|
||||||
|
# Create the site and contacts to delete (orphaned)
|
||||||
|
contact = Contact.objects.create(
|
||||||
|
first_name="Henry",
|
||||||
|
last_name="Mcfakerson",
|
||||||
|
)
|
||||||
|
# Create two non-orphaned contacts
|
||||||
|
contact_2 = Contact.objects.create(
|
||||||
|
first_name="Saturn",
|
||||||
|
last_name="Mars",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attach a user object to a contact (should not be deleted)
|
||||||
|
contact_user, _ = Contact.objects.get_or_create(user=self.user)
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site,
|
||||||
|
status=DomainApplication.ApplicationStatus.WITHDRAWN,
|
||||||
|
authorizing_official=contact,
|
||||||
|
submitter=contact_user,
|
||||||
|
)
|
||||||
|
application.other_contacts.set([contact_2])
|
||||||
|
|
||||||
|
# Make a request to the home page
|
||||||
|
home_page = self.app.get("/")
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Assert that the response contains "city.gov"
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Click the "Edit" link
|
||||||
|
response = home_page.click("Edit", index=0)
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
|
||||||
|
# Check if the response is a redirect
|
||||||
|
if response.status_code == 302:
|
||||||
|
# Follow the redirect manually
|
||||||
|
try:
|
||||||
|
detail_page = response.follow()
|
||||||
|
|
||||||
|
self.wizard.get_context_data()
|
||||||
|
except Exception as err:
|
||||||
|
# Handle any potential errors while following the redirect
|
||||||
|
self.fail(f"Error following the redirect {err}")
|
||||||
|
|
||||||
|
# Now 'detail_page' contains the response after following the redirect
|
||||||
|
self.assertEqual(detail_page.status_code, 200)
|
||||||
|
|
||||||
|
# 5 unlocked steps (ao, domain, submitter, other contacts, and current sites
|
||||||
|
# which unlocks if domain exists), one active step, the review step is locked
|
||||||
|
self.assertContains(detail_page, "#check_circle", count=5)
|
||||||
|
# Type of organization
|
||||||
|
self.assertContains(detail_page, "usa-current", count=1)
|
||||||
|
self.assertContains(detail_page, "link_usa-checked", count=5)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.fail(f"Expected a redirect, but got a different response: {response}")
|
||||||
|
|
||||||
|
|
||||||
|
class HomeTests(TestWithUser):
|
||||||
|
"""A series of tests that target the two tables on home.html"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
Contact.objects.all().delete()
|
||||||
|
|
||||||
|
def test_home_lists_domain_applications(self):
|
||||||
|
response = self.client.get("/")
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(creator=self.user, requested_domain=site)
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# count = 7 because of screenreader content
|
||||||
|
self.assertContains(response, "igorville.gov", count=7)
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_state_help_text(self):
|
||||||
|
"""Tests if each domain state has help text"""
|
||||||
|
|
||||||
|
# Get the expected text content of each state
|
||||||
|
deleted_text = "This domain has been removed and " "is no longer registered to your organization."
|
||||||
|
dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses."
|
||||||
|
ready_text = "This domain has name servers and is ready for use."
|
||||||
|
on_hold_text = (
|
||||||
|
"This domain is administratively paused, "
|
||||||
|
"so it can’t be edited and won’t resolve in DNS. "
|
||||||
|
"Contact help@get.gov for details."
|
||||||
|
)
|
||||||
|
deleted_text = "This domain has been removed and " "is no longer registered to your organization."
|
||||||
|
# Generate a mapping of domain names, the state, and expected messages for the subtest
|
||||||
|
test_cases = [
|
||||||
|
("deleted.gov", Domain.State.DELETED, deleted_text),
|
||||||
|
("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text),
|
||||||
|
("unknown.gov", Domain.State.UNKNOWN, dns_needed_text),
|
||||||
|
("onhold.gov", Domain.State.ON_HOLD, on_hold_text),
|
||||||
|
("ready.gov", Domain.State.READY, ready_text),
|
||||||
|
]
|
||||||
|
for domain_name, state, expected_message in test_cases:
|
||||||
|
with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message):
|
||||||
|
# Create a domain and a UserRole with the given params
|
||||||
|
test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state)
|
||||||
|
test_domain.expiration_date = date.today()
|
||||||
|
test_domain.save()
|
||||||
|
|
||||||
|
user_role, _ = UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, domain_name, count=2)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, expected_message, count=1)
|
||||||
|
|
||||||
|
# Delete the role and domain to ensure we're testing in isolation
|
||||||
|
user_role.delete()
|
||||||
|
test_domain.delete()
|
||||||
|
|
||||||
|
def test_state_help_text_expired(self):
|
||||||
|
"""Tests if each domain state has help text when expired"""
|
||||||
|
expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
|
test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY)
|
||||||
|
test_domain.expiration_date = date(2011, 10, 10)
|
||||||
|
test_domain.save()
|
||||||
|
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, "expired.gov", count=2)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, expired_text, count=1)
|
||||||
|
|
||||||
|
def test_state_help_text_no_expiration_date(self):
|
||||||
|
"""Tests if each domain state has help text when expiration date is None"""
|
||||||
|
|
||||||
|
# == Test a expiration of None for state ready. This should be expired. == #
|
||||||
|
expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
|
test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY)
|
||||||
|
test_domain.expiration_date = None
|
||||||
|
test_domain.save()
|
||||||
|
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, "imexpired.gov", count=2)
|
||||||
|
|
||||||
|
# Make sure the expiration date is None
|
||||||
|
self.assertEqual(test_domain.expiration_date, None)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, expired_text, count=1)
|
||||||
|
|
||||||
|
# == Test a expiration of None for state unknown. This should not display expired text. == #
|
||||||
|
unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses."
|
||||||
|
test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN)
|
||||||
|
test_domain_2.expiration_date = None
|
||||||
|
test_domain_2.save()
|
||||||
|
|
||||||
|
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER)
|
||||||
|
|
||||||
|
# Grab the home page
|
||||||
|
response = self.client.get("/")
|
||||||
|
|
||||||
|
# Make sure the user can actually see the domain.
|
||||||
|
# We expect two instances because of SR content.
|
||||||
|
self.assertContains(response, "notexpired.gov", count=2)
|
||||||
|
|
||||||
|
# Make sure the expiration date is None
|
||||||
|
self.assertEqual(test_domain_2.expiration_date, None)
|
||||||
|
|
||||||
|
# Check that we have the right text content.
|
||||||
|
self.assertContains(response, unknown_text, count=1)
|
||||||
|
|
||||||
|
def test_home_deletes_withdrawn_domain_application(self):
|
||||||
|
"""Tests if the user can delete a DomainApplication in the 'withdrawn' status"""
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that igorville.gov exists on the page
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Check if the delete button exists. We can do this by checking for its id and text content.
|
||||||
|
self.assertContains(home_page, "Delete")
|
||||||
|
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
||||||
|
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_home_deletes_started_domain_application(self):
|
||||||
|
"""Tests if the user can delete a DomainApplication in the 'started' status"""
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.STARTED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that igorville.gov exists on the page
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Check if the delete button exists. We can do this by checking for its id and text content.
|
||||||
|
self.assertContains(home_page, "Delete")
|
||||||
|
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
||||||
|
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_home_doesnt_delete_other_domain_applications(self):
|
||||||
|
"""Tests to ensure the user can't delete Applications not in the status of STARTED or WITHDRAWN"""
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# Make sure the DomainApplication wasn't deleted
|
||||||
|
self.assertEqual(desired_application.count(), 1)
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
application.delete()
|
||||||
|
|
||||||
|
def test_home_deletes_domain_application_and_orphans(self):
|
||||||
|
"""Tests if delete for DomainApplication deletes orphaned Contact objects"""
|
||||||
|
|
||||||
|
# Create the site and contacts to delete (orphaned)
|
||||||
|
contact = Contact.objects.create(
|
||||||
|
first_name="Henry",
|
||||||
|
last_name="Mcfakerson",
|
||||||
|
)
|
||||||
|
contact_shared = Contact.objects.create(
|
||||||
|
first_name="Relative",
|
||||||
|
last_name="Aether",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create two non-orphaned contacts
|
||||||
|
contact_2 = Contact.objects.create(
|
||||||
|
first_name="Saturn",
|
||||||
|
last_name="Mars",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attach a user object to a contact (should not be deleted)
|
||||||
|
contact_user, _ = Contact.objects.get_or_create(user=self.user)
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site,
|
||||||
|
status=DomainApplication.ApplicationStatus.WITHDRAWN,
|
||||||
|
authorizing_official=contact,
|
||||||
|
submitter=contact_user,
|
||||||
|
)
|
||||||
|
application.other_contacts.set([contact_2])
|
||||||
|
|
||||||
|
# Create a second application to attach contacts to
|
||||||
|
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
||||||
|
application_2 = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site_2,
|
||||||
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
|
authorizing_official=contact_2,
|
||||||
|
submitter=contact_shared,
|
||||||
|
)
|
||||||
|
application_2.other_contacts.set([contact_shared])
|
||||||
|
|
||||||
|
# Ensure that igorville.gov exists on the page
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "igorville.gov")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True)
|
||||||
|
|
||||||
|
# igorville is now deleted
|
||||||
|
self.assertNotContains(response, "igorville.gov")
|
||||||
|
|
||||||
|
# Check if the orphaned contact was deleted
|
||||||
|
orphan = Contact.objects.filter(id=contact.id)
|
||||||
|
self.assertFalse(orphan.exists())
|
||||||
|
|
||||||
|
# All non-orphan contacts should still exist and are unaltered
|
||||||
|
try:
|
||||||
|
current_user = Contact.objects.filter(id=contact_user.id).get()
|
||||||
|
except Contact.DoesNotExist:
|
||||||
|
self.fail("contact_user (a non-orphaned contact) was deleted")
|
||||||
|
|
||||||
|
self.assertEqual(current_user, contact_user)
|
||||||
|
try:
|
||||||
|
edge_case = Contact.objects.filter(id=contact_2.id).get()
|
||||||
|
except Contact.DoesNotExist:
|
||||||
|
self.fail("contact_2 (a non-orphaned contact) was deleted")
|
||||||
|
|
||||||
|
self.assertEqual(edge_case, contact_2)
|
||||||
|
|
||||||
|
def test_home_deletes_domain_application_and_shared_orphans(self):
|
||||||
|
"""Test the edge case for an object that will become orphaned after a delete
|
||||||
|
(but is not an orphan at the time of deletion)"""
|
||||||
|
|
||||||
|
# Create the site and contacts to delete (orphaned)
|
||||||
|
contact = Contact.objects.create(
|
||||||
|
first_name="Henry",
|
||||||
|
last_name="Mcfakerson",
|
||||||
|
)
|
||||||
|
contact_shared = Contact.objects.create(
|
||||||
|
first_name="Relative",
|
||||||
|
last_name="Aether",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create two non-orphaned contacts
|
||||||
|
contact_2 = Contact.objects.create(
|
||||||
|
first_name="Saturn",
|
||||||
|
last_name="Mars",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attach a user object to a contact (should not be deleted)
|
||||||
|
contact_user, _ = Contact.objects.get_or_create(user=self.user)
|
||||||
|
|
||||||
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site,
|
||||||
|
status=DomainApplication.ApplicationStatus.WITHDRAWN,
|
||||||
|
authorizing_official=contact,
|
||||||
|
submitter=contact_user,
|
||||||
|
)
|
||||||
|
application.other_contacts.set([contact_2])
|
||||||
|
|
||||||
|
# Create a second application to attach contacts to
|
||||||
|
site_2 = DraftDomain.objects.create(name="teaville.gov")
|
||||||
|
application_2 = DomainApplication.objects.create(
|
||||||
|
creator=self.user,
|
||||||
|
requested_domain=site_2,
|
||||||
|
status=DomainApplication.ApplicationStatus.STARTED,
|
||||||
|
authorizing_official=contact_2,
|
||||||
|
submitter=contact_shared,
|
||||||
|
)
|
||||||
|
application_2.other_contacts.set([contact_shared])
|
||||||
|
|
||||||
|
home_page = self.client.get("/")
|
||||||
|
self.assertContains(home_page, "teaville.gov")
|
||||||
|
|
||||||
|
# Trigger the delete logic
|
||||||
|
response = self.client.post(reverse("application-delete", kwargs={"pk": application_2.pk}), follow=True)
|
||||||
|
|
||||||
|
self.assertNotContains(response, "teaville.gov")
|
||||||
|
|
||||||
|
# Check if the orphaned contact was deleted
|
||||||
|
orphan = Contact.objects.filter(id=contact_shared.id)
|
||||||
|
self.assertFalse(orphan.exists())
|
||||||
|
|
||||||
|
def test_application_form_view(self):
|
||||||
|
response = self.client.get("/request/", follow=True)
|
||||||
|
self.assertContains(
|
||||||
|
response,
|
||||||
|
"You’re about to start your .gov domain request.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_domain_application_form_with_ineligible_user(self):
|
||||||
|
"""Application form not accessible for an ineligible user.
|
||||||
|
This test should be solid enough since all application wizard
|
||||||
|
views share the same permissions class"""
|
||||||
|
self.user.status = User.RESTRICTED
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
with less_console_noise():
|
||||||
|
response = self.client.get("/request/", follow=True)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
|
@ -218,6 +218,7 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
It shows as 'DNS needed'"""
|
It shows as 'DNS needed'"""
|
||||||
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
||||||
# from constructors. Let's reset.
|
# from constructors. Let's reset.
|
||||||
|
with less_console_noise():
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
|
@ -238,6 +239,7 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
It shows as 'DNS needed'"""
|
It shows as 'DNS needed'"""
|
||||||
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
|
||||||
# from constructors. Let's reset.
|
# from constructors. Let's reset.
|
||||||
|
with less_console_noise():
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
|
||||||
|
@ -260,16 +262,17 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
"""We could easily duplicate this test for all domain management
|
"""We could easily duplicate this test for all domain management
|
||||||
views, but a single url test should be solid enough since all domain
|
views, but a single url test should be solid enough since all domain
|
||||||
management pages share the same permissions class"""
|
management pages share the same permissions class"""
|
||||||
|
with less_console_noise():
|
||||||
self.user.status = User.RESTRICTED
|
self.user.status = User.RESTRICTED
|
||||||
self.user.save()
|
self.user.save()
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "igorville.gov")
|
self.assertContains(home_page, "igorville.gov")
|
||||||
with less_console_noise():
|
|
||||||
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_domain_detail_allowed_for_on_hold(self):
|
def test_domain_detail_allowed_for_on_hold(self):
|
||||||
"""Test that the domain overview page displays for on hold domain"""
|
"""Test that the domain overview page displays for on hold domain"""
|
||||||
|
with less_console_noise():
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "on-hold.gov")
|
self.assertContains(home_page, "on-hold.gov")
|
||||||
|
|
||||||
|
@ -278,6 +281,7 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
self.assertNotContains(detail_page, "Edit")
|
self.assertNotContains(detail_page, "Edit")
|
||||||
|
|
||||||
def test_domain_detail_see_just_nameserver(self):
|
def test_domain_detail_see_just_nameserver(self):
|
||||||
|
with less_console_noise():
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "justnameserver.com")
|
self.assertContains(home_page, "justnameserver.com")
|
||||||
|
|
||||||
|
@ -289,6 +293,7 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
self.assertContains(detail_page, "ns2.justnameserver.com")
|
self.assertContains(detail_page, "ns2.justnameserver.com")
|
||||||
|
|
||||||
def test_domain_detail_see_nameserver_and_ip(self):
|
def test_domain_detail_see_nameserver_and_ip(self):
|
||||||
|
with less_console_noise():
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "nameserverwithip.gov")
|
self.assertContains(home_page, "nameserverwithip.gov")
|
||||||
|
|
||||||
|
@ -307,6 +312,7 @@ class TestDomainDetail(TestDomainOverview):
|
||||||
def test_domain_detail_with_no_information_or_application(self):
|
def test_domain_detail_with_no_information_or_application(self):
|
||||||
"""Test that domain management page returns 200 and displays error
|
"""Test that domain management page returns 200 and displays error
|
||||||
when no domain information or domain application exist"""
|
when no domain information or domain application exist"""
|
||||||
|
with less_console_noise():
|
||||||
# have to use staff user for this test
|
# have to use staff user for this test
|
||||||
staff_user = create_user()
|
staff_user = create_user()
|
||||||
# staff_user.save()
|
# staff_user.save()
|
||||||
|
@ -332,7 +338,6 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
self.user.is_staff = False
|
self.user.is_staff = False
|
||||||
self.user.save()
|
self.user.save()
|
||||||
User.objects.all().delete()
|
|
||||||
|
|
||||||
def test_domain_managers(self):
|
def test_domain_managers(self):
|
||||||
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
||||||
|
@ -348,183 +353,6 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||||
self.assertContains(response, "Add a domain manager")
|
self.assertContains(response, "Add a domain manager")
|
||||||
|
|
||||||
def test_domain_user_delete(self):
|
|
||||||
"""Tests if deleting a domain manager works"""
|
|
||||||
|
|
||||||
# Add additional users
|
|
||||||
dummy_user_1 = User.objects.create(
|
|
||||||
username="macncheese",
|
|
||||||
email="cheese@igorville.com",
|
|
||||||
)
|
|
||||||
dummy_user_2 = User.objects.create(
|
|
||||||
username="pastapizza",
|
|
||||||
email="pasta@igorville.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
|
|
||||||
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
|
||||||
|
|
||||||
# Make sure we're on the right page
|
|
||||||
self.assertContains(response, "Domain managers")
|
|
||||||
|
|
||||||
# Make sure the desired user exists
|
|
||||||
self.assertContains(response, "cheese@igorville.com")
|
|
||||||
|
|
||||||
# Delete dummy_user_1
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("domain-user-delete", kwargs={"pk": self.domain.id, "user_pk": dummy_user_1.id}), follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Grab the displayed messages
|
|
||||||
messages = list(response.context["messages"])
|
|
||||||
self.assertEqual(len(messages), 1)
|
|
||||||
|
|
||||||
# Ensure the error we recieve is in line with what we expect
|
|
||||||
message = messages[0]
|
|
||||||
self.assertEqual(message.message, "Removed cheese@igorville.com as a manager for this domain.")
|
|
||||||
self.assertEqual(message.tags, "success")
|
|
||||||
|
|
||||||
# Check that role_1 deleted in the DB after the post
|
|
||||||
deleted_user_exists = UserDomainRole.objects.filter(id=role_1.id).exists()
|
|
||||||
self.assertFalse(deleted_user_exists)
|
|
||||||
|
|
||||||
# Ensure that the current user wasn't deleted
|
|
||||||
current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists()
|
|
||||||
self.assertTrue(current_user_exists)
|
|
||||||
|
|
||||||
# Ensure that the other userdomainrole was not deleted
|
|
||||||
role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists()
|
|
||||||
self.assertTrue(role_2_exists)
|
|
||||||
|
|
||||||
def test_domain_user_delete_denied_if_no_permission(self):
|
|
||||||
"""Deleting a domain manager is denied if the user has no permission to do so"""
|
|
||||||
|
|
||||||
# Create a domain object
|
|
||||||
vip_domain = Domain.objects.create(name="freeman.gov")
|
|
||||||
|
|
||||||
# Add users
|
|
||||||
dummy_user_1 = User.objects.create(
|
|
||||||
username="bagel",
|
|
||||||
email="bagel@igorville.com",
|
|
||||||
)
|
|
||||||
dummy_user_2 = User.objects.create(
|
|
||||||
username="pastapizza",
|
|
||||||
email="pasta@igorville.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=vip_domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=vip_domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
|
|
||||||
response = self.client.get(reverse("domain-users", kwargs={"pk": vip_domain.id}))
|
|
||||||
|
|
||||||
# Make sure that we can't access the domain manager page normally
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
# Try to delete dummy_user_1
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("domain-user-delete", kwargs={"pk": vip_domain.id, "user_pk": dummy_user_1.id}), follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that we are denied access
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
# Ensure that the user wasn't deleted
|
|
||||||
role_1_exists = UserDomainRole.objects.filter(id=role_1.id).exists()
|
|
||||||
self.assertTrue(role_1_exists)
|
|
||||||
|
|
||||||
# Ensure that the other userdomainrole was not deleted
|
|
||||||
role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists()
|
|
||||||
self.assertTrue(role_2_exists)
|
|
||||||
|
|
||||||
# Make sure that the current user wasn't deleted for some reason
|
|
||||||
current_user_exists = UserDomainRole.objects.filter(user=dummy_user_1.id, domain=vip_domain.id).exists()
|
|
||||||
self.assertTrue(current_user_exists)
|
|
||||||
|
|
||||||
def test_domain_user_delete_denied_if_last_man_standing(self):
|
|
||||||
"""Deleting a domain manager is denied if the user is the only manager"""
|
|
||||||
|
|
||||||
# Create a domain object
|
|
||||||
vip_domain = Domain.objects.create(name="olive-oil.gov")
|
|
||||||
|
|
||||||
# Add the requesting user as the only manager on the domain
|
|
||||||
UserDomainRole.objects.create(user=self.user, domain=vip_domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
|
|
||||||
response = self.client.get(reverse("domain-users", kwargs={"pk": vip_domain.id}))
|
|
||||||
|
|
||||||
# Make sure that we can still access the domain manager page normally
|
|
||||||
self.assertContains(response, "Domain managers")
|
|
||||||
|
|
||||||
# Make sure that the logged in user exists
|
|
||||||
self.assertContains(response, "info@example.com")
|
|
||||||
|
|
||||||
# Try to delete the current user
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("domain-user-delete", kwargs={"pk": vip_domain.id, "user_pk": self.user.id}), follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure that we are denied access
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
# Make sure that the current user wasn't deleted
|
|
||||||
current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=vip_domain.id).exists()
|
|
||||||
self.assertTrue(current_user_exists)
|
|
||||||
|
|
||||||
def test_domain_user_delete_self_redirects_home(self):
|
|
||||||
"""Tests if deleting yourself redirects to home"""
|
|
||||||
# Add additional users
|
|
||||||
dummy_user_1 = User.objects.create(
|
|
||||||
username="macncheese",
|
|
||||||
email="cheese@igorville.com",
|
|
||||||
)
|
|
||||||
dummy_user_2 = User.objects.create(
|
|
||||||
username="pastapizza",
|
|
||||||
email="pasta@igorville.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
|
|
||||||
|
|
||||||
response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id}))
|
|
||||||
|
|
||||||
# Make sure we're on the right page
|
|
||||||
self.assertContains(response, "Domain managers")
|
|
||||||
|
|
||||||
# Make sure the desired user exists
|
|
||||||
self.assertContains(response, "info@example.com")
|
|
||||||
|
|
||||||
# Make sure more than one UserDomainRole exists on this object
|
|
||||||
self.assertContains(response, "cheese@igorville.com")
|
|
||||||
|
|
||||||
# Delete the current user
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("domain-user-delete", kwargs={"pk": self.domain.id, "user_pk": self.user.id}), follow=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if we've been redirected to the home page
|
|
||||||
self.assertContains(response, "Manage your domains")
|
|
||||||
|
|
||||||
# Grab the displayed messages
|
|
||||||
messages = list(response.context["messages"])
|
|
||||||
self.assertEqual(len(messages), 1)
|
|
||||||
|
|
||||||
# Ensure the error we recieve is in line with what we expect
|
|
||||||
message = messages[0]
|
|
||||||
self.assertEqual(message.message, "You are no longer managing the domain igorville.gov.")
|
|
||||||
self.assertEqual(message.tags, "success")
|
|
||||||
|
|
||||||
# Ensure that the current user was deleted
|
|
||||||
current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists()
|
|
||||||
self.assertFalse(current_user_exists)
|
|
||||||
|
|
||||||
# Ensure that the other userdomainroles are not deleted
|
|
||||||
role_1_exists = UserDomainRole.objects.filter(id=role_1.id).exists()
|
|
||||||
self.assertTrue(role_1_exists)
|
|
||||||
|
|
||||||
role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists()
|
|
||||||
self.assertTrue(role_2_exists)
|
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_domain_user_add_form(self):
|
def test_domain_user_add_form(self):
|
||||||
"""Adding an existing user works."""
|
"""Adding an existing user works."""
|
||||||
|
@ -1278,6 +1106,7 @@ class TestDomainContactInformation(TestDomainOverview):
|
||||||
class TestDomainSecurityEmail(TestDomainOverview):
|
class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
def test_domain_security_email_existing_security_contact(self):
|
def test_domain_security_email_existing_security_contact(self):
|
||||||
"""Can load domain's security email page."""
|
"""Can load domain's security email page."""
|
||||||
|
with less_console_noise():
|
||||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||||
self.mockedSendFunction = self.mockSendPatch.start()
|
self.mockedSendFunction = self.mockSendPatch.start()
|
||||||
self.mockedSendFunction.side_effect = self.mockSend
|
self.mockedSendFunction.side_effect = self.mockSend
|
||||||
|
@ -1295,6 +1124,7 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
def test_domain_security_email_no_security_contact(self):
|
def test_domain_security_email_no_security_contact(self):
|
||||||
"""Loads a domain with no defined security email.
|
"""Loads a domain with no defined security email.
|
||||||
We should not show the default."""
|
We should not show the default."""
|
||||||
|
with less_console_noise():
|
||||||
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
self.mockSendPatch = patch("registrar.models.domain.registry.send")
|
||||||
self.mockedSendFunction = self.mockSendPatch.start()
|
self.mockedSendFunction = self.mockSendPatch.start()
|
||||||
self.mockedSendFunction.side_effect = self.mockSend
|
self.mockedSendFunction.side_effect = self.mockSend
|
||||||
|
@ -1308,6 +1138,7 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
|
|
||||||
def test_domain_security_email(self):
|
def test_domain_security_email(self):
|
||||||
"""Can load domain's security email page."""
|
"""Can load domain's security email page."""
|
||||||
|
with less_console_noise():
|
||||||
page = self.client.get(reverse("domain-security-email", kwargs={"pk": self.domain.id}))
|
page = self.client.get(reverse("domain-security-email", kwargs={"pk": self.domain.id}))
|
||||||
self.assertContains(page, "Security email")
|
self.assertContains(page, "Security email")
|
||||||
|
|
||||||
|
@ -1315,6 +1146,7 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
"""Adding a security email works.
|
"""Adding a security email works.
|
||||||
Uses self.app WebTest because we need to interact with forms.
|
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}))
|
security_email_page = self.app.get(reverse("domain-security-email", kwargs={"pk": self.domain.id}))
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
security_email_page.form["security_email"] = "mayor@igorville.gov"
|
security_email_page.form["security_email"] = "mayor@igorville.gov"
|
||||||
|
@ -1333,25 +1165,22 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
success_page = result.follow()
|
success_page = result.follow()
|
||||||
self.assertContains(success_page, "The security email for this domain has been updated")
|
self.assertContains(success_page, "The security email for this domain has been updated")
|
||||||
|
|
||||||
def test_security_email_form_messages(self):
|
def test_domain_security_email_form_messages(self):
|
||||||
"""
|
"""
|
||||||
Test against the success and error messages that are defined in the view
|
Test against the success and error messages that are defined in the view
|
||||||
"""
|
"""
|
||||||
|
with less_console_noise():
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
self.client.login(username="superuser", password=p)
|
self.client.login(username="superuser", password=p)
|
||||||
|
|
||||||
form_data_registry_error = {
|
form_data_registry_error = {
|
||||||
"security_email": "test@failCreate.gov",
|
"security_email": "test@failCreate.gov",
|
||||||
}
|
}
|
||||||
|
|
||||||
form_data_contact_error = {
|
form_data_contact_error = {
|
||||||
"security_email": "test@contactError.gov",
|
"security_email": "test@contactError.gov",
|
||||||
}
|
}
|
||||||
|
|
||||||
form_data_success = {
|
form_data_success = {
|
||||||
"security_email": "test@something.gov",
|
"security_email": "test@something.gov",
|
||||||
}
|
}
|
||||||
|
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(
|
(
|
||||||
"RegistryError",
|
"RegistryError",
|
||||||
|
@ -1370,17 +1199,14 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
),
|
),
|
||||||
# Add more test cases with different scenarios here
|
# Add more test cases with different scenarios here
|
||||||
]
|
]
|
||||||
|
|
||||||
for test_name, data, expected_message in test_cases:
|
for test_name, data, expected_message in test_cases:
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("domain-security-email", kwargs={"pk": self.domain.id}),
|
reverse("domain-security-email", kwargs={"pk": self.domain.id}),
|
||||||
data=data,
|
data=data,
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check the response status code, content, or any other relevant assertions
|
# Check the response status code, content, or any other relevant assertions
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Check if the expected message tag is set
|
# Check if the expected message tag is set
|
||||||
if test_name == "RegistryError" or test_name == "ContactError":
|
if test_name == "RegistryError" or test_name == "ContactError":
|
||||||
message_tag = "error"
|
message_tag = "error"
|
||||||
|
@ -1389,7 +1215,6 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
else:
|
else:
|
||||||
# Handle other cases if needed
|
# Handle other cases if needed
|
||||||
message_tag = "info" # Change to the appropriate default
|
message_tag = "info" # Change to the appropriate default
|
||||||
|
|
||||||
# Check the message tag
|
# Check the message tag
|
||||||
messages = list(response.context["messages"])
|
messages = list(response.context["messages"])
|
||||||
self.assertEqual(len(messages), 1)
|
self.assertEqual(len(messages), 1)
|
||||||
|
@ -1411,7 +1236,6 @@ class TestDomainSecurityEmail(TestDomainOverview):
|
||||||
|
|
||||||
|
|
||||||
class TestDomainDNSSEC(TestDomainOverview):
|
class TestDomainDNSSEC(TestDomainOverview):
|
||||||
|
|
||||||
"""MockEPPLib is already inherited."""
|
"""MockEPPLib is already inherited."""
|
||||||
|
|
||||||
def test_dnssec_page_refreshes_enable_button(self):
|
def test_dnssec_page_refreshes_enable_button(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.db.models import F, Value, CharField
|
||||||
from django.db.models.functions import Concat, Coalesce
|
from django.db.models.functions import Concat, Coalesce
|
||||||
|
|
||||||
from registrar.models.public_contact import PublicContact
|
from registrar.models.public_contact import PublicContact
|
||||||
|
from registrar.utility.enums import DefaultEmail
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -17,8 +17,9 @@ logger = logging.getLogger(__name__)
|
||||||
def write_header(writer, columns):
|
def write_header(writer, columns):
|
||||||
"""
|
"""
|
||||||
Receives params from the parent methods and outputs a CSV with a header row.
|
Receives params from the parent methods and outputs a CSV with a header row.
|
||||||
Works with write_header as longas the same writer object is passed.
|
Works with write_header as long as the same writer object is passed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
writer.writerow(columns)
|
writer.writerow(columns)
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ def get_domain_infos(filter_condition, sort_fields):
|
||||||
return domain_infos_cleaned
|
return domain_infos_cleaned
|
||||||
|
|
||||||
|
|
||||||
def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None):
|
def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None, get_domain_managers=False):
|
||||||
"""Given a set of columns, generate a new row from cleaned column data"""
|
"""Given a set of columns, generate a new row from cleaned column data"""
|
||||||
|
|
||||||
# Domain should never be none when parsing this information
|
# Domain should never be none when parsing this information
|
||||||
|
@ -65,7 +66,7 @@ def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None
|
||||||
security_email = _email if _email is not None else " "
|
security_email = _email if _email is not None else " "
|
||||||
|
|
||||||
# These are default emails that should not be displayed in the csv report
|
# These are default emails that should not be displayed in the csv report
|
||||||
invalid_emails = {"registrar@dotgov.gov", "dotgov@cisa.dhs.gov"}
|
invalid_emails = {DefaultEmail.LEGACY_DEFAULT.value, DefaultEmail.PUBLIC_CONTACT_DEFAULT.value}
|
||||||
if security_email.lower() in invalid_emails:
|
if security_email.lower() in invalid_emails:
|
||||||
security_email = "(blank)"
|
security_email = "(blank)"
|
||||||
|
|
||||||
|
@ -77,6 +78,8 @@ def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None
|
||||||
# create a dictionary of fields which can be included in output
|
# create a dictionary of fields which can be included in output
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
"Domain name": domain.name,
|
"Domain name": domain.name,
|
||||||
|
"Status": domain.get_state_display(),
|
||||||
|
"Expiration date": domain.expiration_date,
|
||||||
"Domain type": domain_type,
|
"Domain type": domain_type,
|
||||||
"Agency": domain_info.federal_agency,
|
"Agency": domain_info.federal_agency,
|
||||||
"Organization name": domain_info.organization_name,
|
"Organization name": domain_info.organization_name,
|
||||||
|
@ -85,33 +88,27 @@ def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None
|
||||||
"AO": domain_info.ao, # type: ignore
|
"AO": domain_info.ao, # type: ignore
|
||||||
"AO email": domain_info.authorizing_official.email if domain_info.authorizing_official else " ",
|
"AO email": domain_info.authorizing_official.email if domain_info.authorizing_official else " ",
|
||||||
"Security contact email": security_email,
|
"Security contact email": security_email,
|
||||||
"Status": domain.get_state_display(),
|
|
||||||
"Expiration date": domain.expiration_date,
|
|
||||||
"Created at": domain.created_at,
|
"Created at": domain.created_at,
|
||||||
"First ready": domain.first_ready,
|
"First ready": domain.first_ready,
|
||||||
"Deleted": domain.deleted,
|
"Deleted": domain.deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if get_domain_managers:
|
||||||
|
# Get each domain managers email and add to list
|
||||||
|
dm_emails = [dm.user.email for dm in domain.permissions.all()]
|
||||||
|
|
||||||
|
# Set up the "matching header" + row field data
|
||||||
|
for i, dm_email in enumerate(dm_emails, start=1):
|
||||||
|
FIELDS[f"Domain manager email {i}"] = dm_email
|
||||||
|
|
||||||
row = [FIELDS.get(column, "") for column in columns]
|
row = [FIELDS.get(column, "") for column in columns]
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
def write_body(
|
def _get_security_emails(sec_contact_ids):
|
||||||
writer,
|
|
||||||
columns,
|
|
||||||
sort_fields,
|
|
||||||
filter_condition,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Receives params from the parent methods and outputs a CSV with fltered and sorted domains.
|
Retrieve security contact emails for the given security contact IDs.
|
||||||
Works with write_header as longas the same writer object is passed.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get the domainInfos
|
|
||||||
all_domain_infos = get_domain_infos(filter_condition, sort_fields)
|
|
||||||
|
|
||||||
# Store all security emails to avoid epp calls or excessive filters
|
|
||||||
sec_contact_ids = all_domain_infos.values_list("domain__security_contact_registry_id", flat=True)
|
|
||||||
security_emails_dict = {}
|
security_emails_dict = {}
|
||||||
public_contacts = (
|
public_contacts = (
|
||||||
PublicContact.objects.only("email", "domain__name")
|
PublicContact.objects.only("email", "domain__name")
|
||||||
|
@ -127,14 +124,55 @@ def write_body(
|
||||||
else:
|
else:
|
||||||
logger.warning("csv_export -> Domain was none for PublicContact")
|
logger.warning("csv_export -> Domain was none for PublicContact")
|
||||||
|
|
||||||
|
return security_emails_dict
|
||||||
|
|
||||||
|
|
||||||
|
def update_columns_with_domain_managers(columns, max_dm_count):
|
||||||
|
"""
|
||||||
|
Update the columns list to include "Domain manager email {#}" headers
|
||||||
|
based on the maximum domain manager count.
|
||||||
|
"""
|
||||||
|
for i in range(1, max_dm_count + 1):
|
||||||
|
columns.append(f"Domain manager email {i}")
|
||||||
|
|
||||||
|
|
||||||
|
def write_csv(
|
||||||
|
writer,
|
||||||
|
columns,
|
||||||
|
sort_fields,
|
||||||
|
filter_condition,
|
||||||
|
get_domain_managers=False,
|
||||||
|
should_write_header=True,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Receives params from the parent methods and outputs a CSV with fltered and sorted domains.
|
||||||
|
Works with write_header as longas the same writer object is passed.
|
||||||
|
get_domain_managers: Conditional bc we only use domain manager info for export_data_full_to_csv
|
||||||
|
should_write_header: Conditional bc export_data_growth_to_csv calls write_body twice
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_domain_infos = get_domain_infos(filter_condition, sort_fields)
|
||||||
|
|
||||||
|
# Store all security emails to avoid epp calls or excessive filters
|
||||||
|
sec_contact_ids = all_domain_infos.values_list("domain__security_contact_registry_id", flat=True)
|
||||||
|
|
||||||
|
security_emails_dict = _get_security_emails(sec_contact_ids)
|
||||||
|
|
||||||
# Reduce the memory overhead when performing the write operation
|
# Reduce the memory overhead when performing the write operation
|
||||||
paginator = Paginator(all_domain_infos, 1000)
|
paginator = Paginator(all_domain_infos, 1000)
|
||||||
|
|
||||||
|
if get_domain_managers and len(all_domain_infos) > 0:
|
||||||
|
# We want to get the max amont of domain managers an
|
||||||
|
# account has to set the column header dynamically
|
||||||
|
max_dm_count = max(len(domain_info.domain.permissions.all()) for domain_info in all_domain_infos)
|
||||||
|
update_columns_with_domain_managers(columns, max_dm_count)
|
||||||
|
|
||||||
for page_num in paginator.page_range:
|
for page_num in paginator.page_range:
|
||||||
page = paginator.page(page_num)
|
page = paginator.page(page_num)
|
||||||
rows = []
|
rows = []
|
||||||
for domain_info in page.object_list:
|
for domain_info in page.object_list:
|
||||||
try:
|
try:
|
||||||
row = parse_row(columns, domain_info, security_emails_dict)
|
row = parse_row(columns, domain_info, security_emails_dict, get_domain_managers)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# This should not happen. If it does, just skip this row.
|
# This should not happen. If it does, just skip this row.
|
||||||
|
@ -142,6 +180,9 @@ def write_body(
|
||||||
logger.error("csv_export -> Error when parsing row, domain was None")
|
logger.error("csv_export -> Error when parsing row, domain was None")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if should_write_header:
|
||||||
|
write_header(writer, columns)
|
||||||
|
|
||||||
writer.writerows(rows)
|
writer.writerows(rows)
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,6 +193,8 @@ def export_data_type_to_csv(csv_file):
|
||||||
# define columns to include in export
|
# define columns to include in export
|
||||||
columns = [
|
columns = [
|
||||||
"Domain name",
|
"Domain name",
|
||||||
|
"Status",
|
||||||
|
"Expiration date",
|
||||||
"Domain type",
|
"Domain type",
|
||||||
"Agency",
|
"Agency",
|
||||||
"Organization name",
|
"Organization name",
|
||||||
|
@ -160,9 +203,9 @@ def export_data_type_to_csv(csv_file):
|
||||||
"AO",
|
"AO",
|
||||||
"AO email",
|
"AO email",
|
||||||
"Security contact email",
|
"Security contact email",
|
||||||
"Status",
|
# For domain manager we are pass it in as a parameter below in write_body
|
||||||
"Expiration date",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Coalesce is used to replace federal_type of None with ZZZZZ
|
# Coalesce is used to replace federal_type of None with ZZZZZ
|
||||||
sort_fields = [
|
sort_fields = [
|
||||||
"organization_type",
|
"organization_type",
|
||||||
|
@ -177,8 +220,7 @@ def export_data_type_to_csv(csv_file):
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
write_header(writer, columns)
|
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=True, should_write_header=True)
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
|
||||||
|
|
||||||
|
|
||||||
def export_data_full_to_csv(csv_file):
|
def export_data_full_to_csv(csv_file):
|
||||||
|
@ -209,8 +251,7 @@ def export_data_full_to_csv(csv_file):
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
write_header(writer, columns)
|
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True)
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
|
||||||
|
|
||||||
|
|
||||||
def export_data_federal_to_csv(csv_file):
|
def export_data_federal_to_csv(csv_file):
|
||||||
|
@ -242,8 +283,7 @@ def export_data_federal_to_csv(csv_file):
|
||||||
Domain.State.ON_HOLD,
|
Domain.State.ON_HOLD,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
write_header(writer, columns)
|
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True)
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_start_date():
|
def get_default_start_date():
|
||||||
|
@ -310,6 +350,12 @@ def export_data_growth_to_csv(csv_file, start_date, end_date):
|
||||||
"domain__deleted__gte": start_date_formatted,
|
"domain__deleted__gte": start_date_formatted,
|
||||||
}
|
}
|
||||||
|
|
||||||
write_header(writer, columns)
|
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True)
|
||||||
write_body(writer, columns, sort_fields, filter_condition)
|
write_csv(
|
||||||
write_body(writer, columns, sort_fields_for_deleted_domains, filter_condition_for_deleted_domains)
|
writer,
|
||||||
|
columns,
|
||||||
|
sort_fields_for_deleted_domains,
|
||||||
|
filter_condition_for_deleted_domains,
|
||||||
|
get_domain_managers=False,
|
||||||
|
should_write_header=False,
|
||||||
|
)
|
||||||
|
|
|
@ -10,7 +10,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class EmailSendingError(RuntimeError):
|
class EmailSendingError(RuntimeError):
|
||||||
|
|
||||||
"""Local error for handling all failures when sending email."""
|
"""Local error for handling all failures when sending email."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -26,3 +26,15 @@ class LogCode(Enum):
|
||||||
INFO = 3
|
INFO = 3
|
||||||
DEBUG = 4
|
DEBUG = 4
|
||||||
DEFAULT = 5
|
DEFAULT = 5
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultEmail(Enum):
|
||||||
|
"""Stores the string values of default emails
|
||||||
|
|
||||||
|
Overview of emails:
|
||||||
|
- PUBLIC_CONTACT_DEFAULT: "dotgov@cisa.dhs.gov"
|
||||||
|
- LEGACY_DEFAULT: "registrar@dotgov.gov"
|
||||||
|
"""
|
||||||
|
|
||||||
|
PUBLIC_CONTACT_DEFAULT = "dotgov@cisa.dhs.gov"
|
||||||
|
LEGACY_DEFAULT = "registrar@dotgov.gov"
|
||||||
|
|
|
@ -159,6 +159,10 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
def storage(self):
|
def storage(self):
|
||||||
# marking session as modified on every access
|
# marking session as modified on every access
|
||||||
# so that updates to nested keys are always saved
|
# so that updates to nested keys are always saved
|
||||||
|
# Also - check that self.request.session has the attr
|
||||||
|
# modified to account for test environments calling
|
||||||
|
# view methods
|
||||||
|
if hasattr(self.request.session, "modified"):
|
||||||
self.request.session.modified = True
|
self.request.session.modified = True
|
||||||
return self.request.session.setdefault(self.prefix, {})
|
return self.request.session.setdefault(self.prefix, {})
|
||||||
|
|
||||||
|
@ -211,6 +215,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
if current_url == self.EDIT_URL_NAME and "id" in kwargs:
|
if current_url == self.EDIT_URL_NAME and "id" in kwargs:
|
||||||
del self.storage
|
del self.storage
|
||||||
self.storage["application_id"] = kwargs["id"]
|
self.storage["application_id"] = kwargs["id"]
|
||||||
|
self.storage["step_history"] = self.db_check_for_unlocking_steps()
|
||||||
|
|
||||||
# if accessing this class directly, redirect to the first step
|
# if accessing this class directly, redirect to the first step
|
||||||
# in other words, if `ApplicationWizard` is called as view
|
# in other words, if `ApplicationWizard` is called as view
|
||||||
|
@ -269,6 +274,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
and from the database if `use_db` is True (provided that record exists).
|
and from the database if `use_db` is True (provided that record exists).
|
||||||
An empty form will be provided if neither of those are true.
|
An empty form will be provided if neither of those are true.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"files": files,
|
"files": files,
|
||||||
"prefix": self.steps.current,
|
"prefix": self.steps.current,
|
||||||
|
@ -329,6 +335,43 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
]
|
]
|
||||||
return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses)
|
return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses)
|
||||||
|
|
||||||
|
def db_check_for_unlocking_steps(self):
|
||||||
|
"""Helper for get_context_data
|
||||||
|
|
||||||
|
Queries the DB for an application and returns a list of unlocked steps."""
|
||||||
|
history_dict = {
|
||||||
|
"organization_type": self.application.organization_type is not None,
|
||||||
|
"tribal_government": self.application.tribe_name is not None,
|
||||||
|
"organization_federal": self.application.federal_type is not None,
|
||||||
|
"organization_election": self.application.is_election_board is not None,
|
||||||
|
"organization_contact": (
|
||||||
|
self.application.federal_agency is not None
|
||||||
|
or self.application.organization_name is not None
|
||||||
|
or self.application.address_line1 is not None
|
||||||
|
or self.application.city is not None
|
||||||
|
or self.application.state_territory is not None
|
||||||
|
or self.application.zipcode is not None
|
||||||
|
or self.application.urbanization is not None
|
||||||
|
),
|
||||||
|
"about_your_organization": self.application.about_your_organization is not None,
|
||||||
|
"authorizing_official": self.application.authorizing_official is not None,
|
||||||
|
"current_sites": (
|
||||||
|
self.application.current_websites.exists() or self.application.requested_domain is not None
|
||||||
|
),
|
||||||
|
"dotgov_domain": self.application.requested_domain is not None,
|
||||||
|
"purpose": self.application.purpose is not None,
|
||||||
|
"your_contact": self.application.submitter is not None,
|
||||||
|
"other_contacts": (
|
||||||
|
self.application.other_contacts.exists() or self.application.no_other_contacts_rationale is not None
|
||||||
|
),
|
||||||
|
"anything_else": (
|
||||||
|
self.application.anything_else is not None or self.application.is_policy_acknowledged is not None
|
||||||
|
),
|
||||||
|
"requirements": self.application.is_policy_acknowledged is not None,
|
||||||
|
"review": self.application.is_policy_acknowledged is not None,
|
||||||
|
}
|
||||||
|
return [key for key, value in history_dict.items() if value]
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
"""Define context for access on all wizard pages."""
|
"""Define context for access on all wizard pages."""
|
||||||
# Build the submit button that we'll pass to the modal.
|
# Build the submit button that we'll pass to the modal.
|
||||||
|
@ -338,6 +381,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView):
|
||||||
modal_heading = "You are about to submit a domain request for " + str(self.application.requested_domain)
|
modal_heading = "You are about to submit a domain request for " + str(self.application.requested_domain)
|
||||||
else:
|
else:
|
||||||
modal_heading = "You are about to submit an incomplete request"
|
modal_heading = "You are about to submit an incomplete request"
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"form_titles": self.TITLES,
|
"form_titles": self.TITLES,
|
||||||
"steps": self.steps,
|
"steps": self.steps,
|
||||||
|
|
|
@ -22,6 +22,7 @@ from registrar.models import (
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
)
|
)
|
||||||
from registrar.models.public_contact import PublicContact
|
from registrar.models.public_contact import PublicContact
|
||||||
|
from registrar.utility.enums import DefaultEmail
|
||||||
from registrar.utility.errors import (
|
from registrar.utility.errors import (
|
||||||
GenericError,
|
GenericError,
|
||||||
GenericErrorCodes,
|
GenericErrorCodes,
|
||||||
|
@ -134,7 +135,6 @@ class DomainFormBaseView(DomainBaseView, FormMixin):
|
||||||
|
|
||||||
|
|
||||||
class DomainView(DomainBaseView):
|
class DomainView(DomainBaseView):
|
||||||
|
|
||||||
"""Domain detail overview page."""
|
"""Domain detail overview page."""
|
||||||
|
|
||||||
template_name = "domain_detail.html"
|
template_name = "domain_detail.html"
|
||||||
|
@ -142,11 +142,12 @@ class DomainView(DomainBaseView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
default_email = self.object.get_default_security_contact().email
|
default_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
||||||
context["default_security_email"] = default_email
|
|
||||||
|
context["hidden_security_emails"] = default_emails
|
||||||
|
|
||||||
security_email = self.object.get_security_email()
|
security_email = self.object.get_security_email()
|
||||||
if security_email is None or security_email == default_email:
|
if security_email is None or security_email in default_emails:
|
||||||
context["security_email"] = None
|
context["security_email"] = None
|
||||||
return context
|
return context
|
||||||
context["security_email"] = security_email
|
context["security_email"] = security_email
|
||||||
|
@ -553,7 +554,7 @@ class DomainYourContactInformationView(DomainFormBaseView):
|
||||||
# Post to DB using values from the form
|
# Post to DB using values from the form
|
||||||
form.save()
|
form.save()
|
||||||
|
|
||||||
messages.success(self.request, "Your contact information has been updated.")
|
messages.success(self.request, "Your contact information for all your domains has been updated.")
|
||||||
|
|
||||||
# superclass has the redirect
|
# superclass has the redirect
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
@ -570,7 +571,7 @@ class DomainSecurityEmailView(DomainFormBaseView):
|
||||||
initial = super().get_initial()
|
initial = super().get_initial()
|
||||||
security_contact = self.object.security_contact
|
security_contact = self.object.security_contact
|
||||||
|
|
||||||
invalid_emails = ["dotgov@cisa.dhs.gov", "registrar@dotgov.gov"]
|
invalid_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
||||||
if security_contact is None or security_contact.email in invalid_emails:
|
if security_contact is None or security_contact.email in invalid_emails:
|
||||||
initial["security_email"] = None
|
initial["security_email"] = None
|
||||||
return initial
|
return initial
|
||||||
|
@ -785,14 +786,17 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMessageMixin):
|
# The order of the superclasses matters here. BaseDeleteView has a bug where the
|
||||||
|
# "form_valid" function does not call super, so it cannot use SuccessMessageMixin.
|
||||||
|
# The workaround is to use SuccessMessageMixin first.
|
||||||
|
class DomainInvitationDeleteView(SuccessMessageMixin, DomainInvitationPermissionDeleteView):
|
||||||
object: DomainInvitation # workaround for type mismatch in DeleteView
|
object: DomainInvitation # workaround for type mismatch in DeleteView
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("domain-users", kwargs={"pk": self.object.domain.id})
|
return reverse("domain-users", kwargs={"pk": self.object.domain.id})
|
||||||
|
|
||||||
def get_success_message(self, cleaned_data):
|
def get_success_message(self, cleaned_data):
|
||||||
return f"Successfully canceled invitation for {self.object.email}."
|
return f"Canceled invitation to {self.object.email}."
|
||||||
|
|
||||||
|
|
||||||
class DomainDeleteUserView(UserDomainRolePermissionDeleteView):
|
class DomainDeleteUserView(UserDomainRolePermissionDeleteView):
|
||||||
|
|
|
@ -146,7 +146,6 @@ class OrderableFieldsMixin:
|
||||||
|
|
||||||
|
|
||||||
class PermissionsLoginMixin(PermissionRequiredMixin):
|
class PermissionsLoginMixin(PermissionRequiredMixin):
|
||||||
|
|
||||||
"""Mixin that redirects to login page if not logged in, otherwise 403."""
|
"""Mixin that redirects to login page if not logged in, otherwise 403."""
|
||||||
|
|
||||||
def handle_no_permission(self):
|
def handle_no_permission(self):
|
||||||
|
@ -155,7 +154,6 @@ class PermissionsLoginMixin(PermissionRequiredMixin):
|
||||||
|
|
||||||
|
|
||||||
class DomainPermission(PermissionsLoginMixin):
|
class DomainPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
"""Permission mixin that redirects to domain if user has access,
|
"""Permission mixin that redirects to domain if user has access,
|
||||||
otherwise 403"""
|
otherwise 403"""
|
||||||
|
|
||||||
|
@ -264,7 +262,6 @@ class DomainPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationPermission(PermissionsLoginMixin):
|
class DomainApplicationPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
"""Permission mixin that redirects to domain application if user
|
"""Permission mixin that redirects to domain application if user
|
||||||
has access, otherwise 403"""
|
has access, otherwise 403"""
|
||||||
|
|
||||||
|
@ -287,7 +284,6 @@ class DomainApplicationPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteDomainRolePermission(PermissionsLoginMixin):
|
class UserDeleteDomainRolePermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
"""Permission mixin for UserDomainRole if user
|
"""Permission mixin for UserDomainRole if user
|
||||||
has access, otherwise 403"""
|
has access, otherwise 403"""
|
||||||
|
|
||||||
|
@ -324,7 +320,6 @@ class UserDeleteDomainRolePermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
|
class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
|
||||||
|
|
||||||
"""Permission mixin that redirects to withdraw action on domain application
|
"""Permission mixin that redirects to withdraw action on domain application
|
||||||
if user has access, otherwise 403"""
|
if user has access, otherwise 403"""
|
||||||
|
|
||||||
|
@ -347,7 +342,6 @@ class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
|
||||||
|
|
||||||
|
|
||||||
class ApplicationWizardPermission(PermissionsLoginMixin):
|
class ApplicationWizardPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
"""Permission mixin that redirects to start or edit domain application if
|
"""Permission mixin that redirects to start or edit domain application if
|
||||||
user has access, otherwise 403"""
|
user has access, otherwise 403"""
|
||||||
|
|
||||||
|
@ -365,7 +359,6 @@ class ApplicationWizardPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitationPermission(PermissionsLoginMixin):
|
class DomainInvitationPermission(PermissionsLoginMixin):
|
||||||
|
|
||||||
"""Permission mixin that redirects to domain invitation if user has
|
"""Permission mixin that redirects to domain invitation if user has
|
||||||
access, otherwise 403"
|
access, otherwise 403"
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
|
class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract base view for domains that enforces permissions.
|
"""Abstract base view for domains that enforces permissions.
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
|
@ -58,7 +57,6 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, abc.ABC):
|
class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract base view for domain applications that enforces permissions
|
"""Abstract base view for domain applications that enforces permissions
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
|
@ -78,7 +76,6 @@ class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, a
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdraw, DetailView, abc.ABC):
|
class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdraw, DetailView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract base view for domain application withdraw function
|
"""Abstract base view for domain application withdraw function
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
|
@ -98,7 +95,6 @@ class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdra
|
||||||
|
|
||||||
|
|
||||||
class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView, abc.ABC):
|
class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract base view for the application form that enforces permissions
|
"""Abstract base view for the application form that enforces permissions
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
|
@ -113,7 +109,6 @@ class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView,
|
||||||
|
|
||||||
|
|
||||||
class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteView, abc.ABC):
|
class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract view for deleting a domain invitation.
|
"""Abstract view for deleting a domain invitation.
|
||||||
|
|
||||||
This one is fairly specialized, but this is the only thing that we do
|
This one is fairly specialized, but this is the only thing that we do
|
||||||
|
@ -127,7 +122,6 @@ class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteVie
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteView, abc.ABC):
|
class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract view for deleting a DomainApplication."""
|
"""Abstract view for deleting a DomainApplication."""
|
||||||
|
|
||||||
model = DomainApplication
|
model = DomainApplication
|
||||||
|
@ -135,7 +129,6 @@ class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteV
|
||||||
|
|
||||||
|
|
||||||
class UserDomainRolePermissionDeleteView(UserDeleteDomainRolePermission, DeleteView, abc.ABC):
|
class UserDomainRolePermissionDeleteView(UserDeleteDomainRolePermission, DeleteView, abc.ABC):
|
||||||
|
|
||||||
"""Abstract base view for deleting a UserDomainRole.
|
"""Abstract base view for deleting a UserDomainRole.
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
This abstract view cannot be instantiated. Actual views must specify
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
-i https://pypi.python.org/simple
|
-i https://pypi.python.org/simple
|
||||||
annotated-types==0.6.0; python_version >= '3.8'
|
annotated-types==0.6.0; python_version >= '3.8'
|
||||||
asgiref==3.7.2; python_version >= '3.7'
|
asgiref==3.7.2; python_version >= '3.7'
|
||||||
boto3==1.33.7; python_version >= '3.7'
|
boto3==1.34.37; python_version >= '3.8'
|
||||||
botocore==1.33.7; python_version >= '3.7'
|
botocore==1.34.37; python_version >= '3.8'
|
||||||
cachetools==5.3.2; python_version >= '3.7'
|
cachetools==5.3.2; python_version >= '3.7'
|
||||||
certifi==2023.11.17; python_version >= '3.6'
|
certifi==2024.2.2; python_version >= '3.6'
|
||||||
cfenv==0.5.3
|
cfenv==0.5.3
|
||||||
cffi==1.16.0; python_version >= '3.8'
|
cffi==1.16.0; platform_python_implementation != 'PyPy'
|
||||||
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
|
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
|
||||||
cryptography==41.0.7; python_version >= '3.7'
|
cryptography==42.0.2; python_version >= '3.7'
|
||||||
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||||
dj-database-url==2.1.0
|
dj-database-url==2.1.0
|
||||||
dj-email-url==1.0.6
|
dj-email-url==1.0.6
|
||||||
django==4.2.7; python_version >= '3.8'
|
django==4.2.10; python_version >= '3.8'
|
||||||
django-allow-cidr==0.7.1
|
django-allow-cidr==0.7.1
|
||||||
django-auditlog==2.3.0; python_version >= '3.7'
|
django-auditlog==2.3.0; python_version >= '3.7'
|
||||||
django-cache-url==3.4.5
|
django-cache-url==3.4.5
|
||||||
|
@ -20,42 +20,42 @@ django-cors-headers==4.3.1; python_version >= '3.8'
|
||||||
django-csp==3.7
|
django-csp==3.7
|
||||||
django-fsm==2.8.1
|
django-fsm==2.8.1
|
||||||
django-login-required-middleware==0.9.0
|
django-login-required-middleware==0.9.0
|
||||||
django-phonenumber-field[phonenumberslite]==7.2.0; python_version >= '3.8'
|
django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8'
|
||||||
django-widget-tweaks==1.5.0; python_version >= '3.8'
|
django-widget-tweaks==1.5.0; python_version >= '3.8'
|
||||||
environs[django]==9.5.0; python_version >= '3.6'
|
environs[django]==10.3.0; python_version >= '3.8'
|
||||||
faker==20.1.0; python_version >= '3.8'
|
faker==23.1.0; python_version >= '3.8'
|
||||||
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
|
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
|
||||||
furl==2.1.3
|
furl==2.1.3
|
||||||
future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
gevent==23.9.1; python_version >= '3.8'
|
gevent==23.9.1; python_version >= '3.8'
|
||||||
geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4
|
geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4
|
||||||
greenlet==3.0.1; python_version >= '3.7'
|
greenlet==3.0.3; python_version >= '3.7'
|
||||||
gunicorn==21.2.0; python_version >= '3.5'
|
gunicorn==21.2.0; python_version >= '3.5'
|
||||||
idna==3.6; python_version >= '3.5'
|
idna==3.6; python_version >= '3.5'
|
||||||
jmespath==1.0.1; python_version >= '3.7'
|
jmespath==1.0.1; python_version >= '3.7'
|
||||||
lxml==4.9.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
lxml==5.1.0; python_version >= '3.6'
|
||||||
mako==1.3.0; python_version >= '3.8'
|
mako==1.3.2; python_version >= '3.8'
|
||||||
markupsafe==2.1.3; python_version >= '3.7'
|
markupsafe==2.1.5; python_version >= '3.7'
|
||||||
marshmallow==3.20.1; python_version >= '3.8'
|
marshmallow==3.20.2; python_version >= '3.8'
|
||||||
oic==1.6.1; python_version ~= '3.7'
|
oic==1.6.1; python_version ~= '3.7'
|
||||||
orderedmultidict==1.0.1
|
orderedmultidict==1.0.1
|
||||||
packaging==23.2; python_version >= '3.7'
|
packaging==23.2; python_version >= '3.7'
|
||||||
phonenumberslite==8.13.26
|
phonenumberslite==8.13.29
|
||||||
psycopg2-binary==2.9.9; python_version >= '3.7'
|
psycopg2-binary==2.9.9; python_version >= '3.7'
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
pycryptodomex==3.19.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||||
pydantic==2.5.2; python_version >= '3.7'
|
pydantic==2.6.1; python_version >= '3.8'
|
||||||
pydantic-core==2.14.5; python_version >= '3.7'
|
pydantic-core==2.16.2; python_version >= '3.8'
|
||||||
pydantic-settings==2.1.0; python_version >= '3.8'
|
pydantic-settings==2.1.0; python_version >= '3.8'
|
||||||
pyjwkest==1.4.2
|
pyjwkest==1.4.2
|
||||||
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
python-dotenv==1.0.0; python_version >= '3.8'
|
python-dotenv==1.0.1; python_version >= '3.8'
|
||||||
requests==2.31.0; python_version >= '3.7'
|
requests==2.31.0; python_version >= '3.7'
|
||||||
s3transfer==0.8.2; python_version >= '3.7'
|
s3transfer==0.10.0; python_version >= '3.8'
|
||||||
setuptools==69.0.2; python_version >= '3.8'
|
setuptools==69.0.3; python_version >= '3.8'
|
||||||
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
sqlparse==0.4.4; python_version >= '3.5'
|
sqlparse==0.4.4; python_version >= '3.5'
|
||||||
typing-extensions==4.8.0; python_version >= '3.8'
|
typing-extensions==4.9.0; python_version >= '3.8'
|
||||||
urllib3==2.0.7; python_version >= '3.7'
|
urllib3==2.0.7; python_version >= '3.7'
|
||||||
whitenoise==6.6.0; python_version >= '3.8'
|
whitenoise==6.6.0; python_version >= '3.8'
|
||||||
zope.event==5.0; python_version >= '3.7'
|
zope.event==5.0; python_version >= '3.7'
|
||||||
|
|
|
@ -6,4 +6,4 @@ set -o pipefail
|
||||||
# Make sure that django's `collectstatic` has been run locally before pushing up to any environment,
|
# Make sure that django's `collectstatic` has been run locally before pushing up to any environment,
|
||||||
# so that the styles and static assets to show up correctly on any environment.
|
# so that the styles and static assets to show up correctly on any environment.
|
||||||
|
|
||||||
gunicorn registrar.config.wsgi -t 60
|
gunicorn --worker-class=gevent registrar.config.wsgi -t 60
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue