mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-16 01:27:03 +02:00
Test case refactor
This commit is contained in:
parent
a64732a301
commit
cf92d4d7e9
4 changed files with 86 additions and 36 deletions
|
@ -221,5 +221,5 @@ class DomainFixture(DomainApplicationFixture):
|
||||||
|
|
||||||
# We don't want fixtures sending out real emails to
|
# We don't want fixtures sending out real emails to
|
||||||
# fake email addresses, so we just skip that and log it instead
|
# fake email addresses, so we just skip that and log it instead
|
||||||
application.approve(do_fake_send_email=True)
|
application.approve(send_email=False)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
|
@ -561,7 +561,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
return not self.approved_domain.is_active()
|
return not self.approved_domain.is_active()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _send_status_update_email(self, new_status, email_template, email_template_subject, do_fake_send_email=False):
|
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.
|
||||||
|
|
||||||
The email goes to the email address that the submitter gave as their
|
The email goes to the email address that the submitter gave as their
|
||||||
|
@ -573,7 +573,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
logger.warning(f"Cannot send {new_status} email, no submitter email address.")
|
logger.warning(f"Cannot send {new_status} email, no submitter email address.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if do_fake_send_email:
|
if not send_email:
|
||||||
logger.info(f"Email was not sent. Would send {new_status} email: {self.submitter.email}")
|
logger.info(f"Email was not sent. Would send {new_status} email: {self.submitter.email}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -676,7 +676,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.APPROVED,
|
target=ApplicationStatus.APPROVED,
|
||||||
)
|
)
|
||||||
def approve(self, do_fake_send_email=False):
|
def approve(self, send_email=True):
|
||||||
"""Approve an application that has been submitted.
|
"""Approve an application that has been submitted.
|
||||||
|
|
||||||
This has substantial side-effects because it creates another database
|
This has substantial side-effects because it creates another database
|
||||||
|
@ -705,7 +705,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
"application approved",
|
"application approved",
|
||||||
"emails/status_change_approved.txt",
|
"emails/status_change_approved.txt",
|
||||||
"emails/status_change_approved_subject.txt",
|
"emails/status_change_approved_subject.txt",
|
||||||
do_fake_send_email,
|
send_email,
|
||||||
)
|
)
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
|
|
|
@ -19,8 +19,8 @@ from registrar.models.transition_domain import TransitionDomain # type: ignore
|
||||||
from .common import MockSESClient, less_console_noise, completed_application
|
from .common import MockSESClient, less_console_noise, completed_application
|
||||||
from django_fsm import TransitionNotAllowed
|
from django_fsm import TransitionNotAllowed
|
||||||
|
|
||||||
boto3_mocking.clients.register_handler("sesv2", MockSESClient)
|
|
||||||
|
|
||||||
|
boto3_mocking.clients.register_handler("sesv2", MockSESClient)
|
||||||
|
|
||||||
# The DomainApplication submit method has a side effect of sending an email
|
# The DomainApplication submit method has a side effect of sending an email
|
||||||
# with AWS SES, so mock that out in all of these test cases
|
# with AWS SES, so mock that out in all of these test cases
|
||||||
|
@ -52,6 +52,12 @@ class TestDomainApplication(TestCase):
|
||||||
status=DomainApplication.ApplicationStatus.INELIGIBLE, name="ineligible.gov"
|
status=DomainApplication.ApplicationStatus.INELIGIBLE, name="ineligible.gov"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.mock_client = MockSESClient()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.mock_client.EMAILS_SENT.clear()
|
||||||
|
|
||||||
def assertNotRaises(self, exception_type):
|
def assertNotRaises(self, exception_type):
|
||||||
"""Helper method for testing allowed transitions."""
|
"""Helper method for testing allowed transitions."""
|
||||||
return self.assertRaises(Exception, None, exception_type)
|
return self.assertRaises(Exception, None, exception_type)
|
||||||
|
@ -130,8 +136,8 @@ class TestDomainApplication(TestCase):
|
||||||
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)
|
||||||
|
|
||||||
mock_client = MockSESClient
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
# can't submit an application with a null domain name
|
# can't submit an application with a null domain name
|
||||||
|
@ -143,8 +149,8 @@ class TestDomainApplication(TestCase):
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=site)
|
application = DomainApplication.objects.create(creator=user, requested_domain=site)
|
||||||
|
|
||||||
# no submitter email so this emits a log warning
|
# no submitter email so this emits a log warning
|
||||||
mock_client = MockSESClient
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
||||||
|
@ -161,7 +167,8 @@ class TestDomainApplication(TestCase):
|
||||||
)
|
)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
|
|
||||||
|
@ -188,7 +195,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -208,7 +216,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -227,7 +236,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -246,7 +256,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -264,7 +275,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -284,7 +296,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -302,7 +315,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -311,6 +325,19 @@ class TestDomainApplication(TestCase):
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
def test_approved_skips_sending_email(self):
|
||||||
|
"""
|
||||||
|
Test that calling .approve with send_email=False doesn't actually send
|
||||||
|
an email
|
||||||
|
"""
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with less_console_noise():
|
||||||
|
self.submitted_application.approve(send_email=False)
|
||||||
|
|
||||||
|
# Assert that no emails were sent
|
||||||
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 0)
|
||||||
|
|
||||||
def test_approved_transition_not_allowed(self):
|
def test_approved_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
|
@ -322,7 +349,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -339,7 +367,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -360,7 +389,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -377,7 +407,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -398,7 +429,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -416,7 +448,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -436,7 +469,8 @@ class TestDomainApplication(TestCase):
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_application, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
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):
|
||||||
|
@ -455,7 +489,8 @@ class TestDomainApplication(TestCase):
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
return True # Override to return True
|
return True # Override to return True
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Use patch to temporarily replace is_active with the custom implementation
|
# Use patch to temporarily replace is_active with the custom implementation
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
|
@ -475,7 +510,8 @@ class TestDomainApplication(TestCase):
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
return True # Override to return True
|
return True # Override to return True
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Use patch to temporarily replace is_active with the custom implementation
|
# Use patch to temporarily replace is_active with the custom implementation
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
|
@ -485,17 +521,24 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestPermissions(TestCase):
|
class TestPermissions(TestCase):
|
||||||
|
|
||||||
"""Test the User-Domain-Role connection."""
|
"""Test the User-Domain-Role connection."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.mock_client = MockSESClient()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.mock_client.EMAILS_SENT.clear()
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_approval_creates_role(self):
|
def test_approval_creates_role(self):
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
|
|
||||||
mock_client = MagicMock()
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
|
@ -510,14 +553,22 @@ class TestDomainInfo(TestCase):
|
||||||
|
|
||||||
"""Test creation of Domain Information when approved."""
|
"""Test creation of Domain Information when approved."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.mock_client = MockSESClient()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.mock_client.EMAILS_SENT.clear()
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_approval_creates_info(self):
|
def test_approval_creates_info(self):
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
|
|
||||||
mock_client = MagicMock()
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
|
|
|
@ -29,7 +29,7 @@ from epplibwrapper import (
|
||||||
RegistryError,
|
RegistryError,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
)
|
)
|
||||||
from .common import MockEppLib, less_console_noise
|
from .common import MockEppLib, MockSESClient, less_console_noise
|
||||||
import logging
|
import logging
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
|
||||||
|
@ -263,8 +263,7 @@ class TestDomainCreation(MockEppLib):
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
|
|
||||||
mock_client = MagicMock()
|
with boto3_mocking.clients.handler_for("sesv2", MockSESClient):
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue