Repository: cisagov/manage.get.gov

Issue:
Update /availability API to pull from new registry
### Issue Description

The [current API](https://github.com/cisagov/getgov/blob/784cc0f618e056c262512d688e8e4316dd25c9e4/src/api/views.py#L14) consists of a second-hand pull of canonical data that the .gov program [publishes to GitHub](https://github.com/cisagov/dotgov-data/blob/main/current-full.csv). Change this implementation so that queries poll the new registry/Whois/RDAP.

### Acceptance Criteria

- [x] The checkDomain method is used to check that a domain is in the registry or not

and

- [x] The check is used at /availability 
- [x] Tests are implemented and/or updated
- [ ]  In the new domain application flow, where a user is checking if a domain is available for use, it should now use this /availability endpoint (if not already) and tests should be updated to check that /availability and epp is being triggered on this page as well. 

### Additional Context (optional)

Once done, the backend work of #476 and frontend work of #561 can be completed.

_Consider add-on to defend the endpoint from bot spam._

### Implementation Notes

Epp has a check command that can be run with just the name of a given domain.  This should be used as the method for checking the desired domain at this endpoint.

### Issue Links

Blocking #476 and #561
Blocked by #1028 


Discussion:
Just some background

src/registrar/config/urls.py sets available endpoint and ties it to the "available" function in the api library
find this line:
path("api/v1/available/<domain>", available, name="available"),

The api library defines a function available under src/api/views
->note it requires a user to be logged in to access this endpoint as is (we may want to change this)
- this function calls in_domains which calls _domains(). "_domains" is where you want to remove the communication with github. In it's place you will use Domain.available(). (surround it with a try and catch as errors can occur).

Resolving patch:
diff --git a/src/api/views.py b/src/api/views.py
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -59,12 +59,12 @@ def in_domains(domain):
     given domain doesn't end with .gov, ".gov" is added when looking for
     a match.
     """
-    domain = domain.lower()
+    Domain = apps.get_model("registrar.Domain")
     if domain.endswith(".gov"):
-        return domain.lower() in _domains()
+        return Domain.available(domain)
     else:
         # domain search string doesn't end with .gov, add it on here
-        return (domain + ".gov") in _domains()
+        return Domain.available(domain + ".gov")
 
 
 @require_http_methods(["GET"])


Test patch:
diff --git a/src/api/tests/test_available.py b/src/api/tests/test_available.py
--- a/src/api/tests/test_available.py
+++ b/src/api/tests/test_available.py
@@ -3,19 +3,27 @@
 import json
 
 from django.contrib.auth import get_user_model
-from django.test import TestCase, RequestFactory
+from django.test import RequestFactory
 
-from ..views import available, _domains, in_domains
+from ..views import available, in_domains
 from .common import less_console_noise
+from registrar.tests.common import MockEppLib
+from unittest.mock import call
+
+from epplibwrapper import (
+    commands,
+    RegistryError,
+)
 
 API_BASE_PATH = "/api/v1/available/"
 
 
-class AvailableViewTest(TestCase):
+class AvailableViewTest(MockEppLib):
 
     """Test that the view function works as expected."""
 
     def setUp(self):
+        super().setUp()
         self.user = get_user_model().objects.create(username="username")
         self.factory = RequestFactory()
 
@@ -29,26 +37,37 @@ def test_view_function(self):
         response_object = json.loads(response.content)
         self.assertIn("available", response_object)
 
-    def test_domain_list(self):
-        """Test the domain list that is returned from Github.
-
-        This does not mock out the external file, it is actually fetched from
-        the internet.
-        """
-        domains = _domains()
-        self.assertIn("gsa.gov", domains)
-        # entries are all lowercase so GSA.GOV is not in the set
-        self.assertNotIn("GSA.GOV", domains)
-        self.assertNotIn("igorvilleremixed.gov", domains)
-        # all the entries have dots
-        self.assertNotIn("gsa", domains)
-
-    def test_in_domains(self):
+    def test_in_domains_makes_calls_(self):
+        """Domain searches successfully make correct mock EPP calls"""
+        gsa_available = in_domains("gsa.gov")
+        igorville_available = in_domains("igorvilleremixed.gov")
+
+        """Domain searches successfully make mock EPP calls"""
+        self.mockedSendFunction.assert_has_calls(
+            [
+                call(
+                    commands.CheckDomain(
+                        ["gsa.gov"],
+                    ),
+                    cleaned=True,
+                ),
+                call(
+                    commands.CheckDomain(
+                        ["igorvilleremixed.gov"],
+                    ),
+                    cleaned=True,
+                ),
+            ]
+        )
+        """Domain searches return correct availability results"""
+        self.assertTrue(gsa_available)
+        self.assertFalse(igorville_available)
+
+    def test_in_domains_capitalized(self):
+        """Domain searches work without case sensitivity"""
         self.assertTrue(in_domains("gsa.gov"))
         # input is lowercased so GSA.GOV should be found
-        self.assertTrue(in_domains("GSA.GOV"))
-        # This domain should not have been registered
-        self.assertFalse(in_domains("igorvilleremixed.gov"))
+        self.assertTrue(in_domains("GSA.gov"))
 
     def test_in_domains_dotgov(self):
         """Domain searches work without trailing .gov"""
@@ -86,13 +105,18 @@ def test_error_handling(self):
         request.user = self.user
         response = available(request, domain=bad_string)
         self.assertFalse(json.loads(response.content)["available"])
+        # domain set to raise error successfully raises error
+        with self.assertRaises(RegistryError):
+            error_domain_available = available(request, "errordomain.gov")
+            self.assertFalse(json.loads(error_domain_available.content)["available"])
 
 
-class AvailableAPITest(TestCase):
+class AvailableAPITest(MockEppLib):
 
     """Test that the API can be called as expected."""
 
     def setUp(self):
+        super().setUp()
         self.user = get_user_model().objects.create(username="username")
 
     def test_available_get(self):
diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py
--- a/src/registrar/tests/common.py
+++ b/src/registrar/tests/common.py
@@ -31,6 +31,7 @@
     info,
     RegistryError,
     ErrorCode,
+    responses,
 )
 
 from registrar.models.utility.contact_error import ContactError, ContactErrorCodes
@@ -784,45 +785,63 @@ def dummyInfoContactResultData(
         ],
     )
 
+    def _mockDomainName(self, _name, _avail=False):
+        return MagicMock(
+            res_data=[
+                responses.check.CheckDomainResultData(
+                    name=_name, avail=_avail, reason=None
+                ),
+            ]
+        )
+
+    def mockCheckDomainCommand(self, _request, cleaned):
+        if "gsa.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("gsa.gov", True)
+        elif "GSA.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("GSA.gov", True)
+        elif "igorvilleremixed.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("igorvilleremixed.gov", False)
+        elif "errordomain.gov" in getattr(_request, "names", None):
+            raise RegistryError("Registry cannot find domain availability.")
+        else:
+            return self._mockDomainName("domainnotfound.gov", False)
+
     def mockSend(self, _request, cleaned):
         """Mocks the registry.send function used inside of domain.py
         registry is imported from epplibwrapper
         returns objects that simulate what would be in a epp response
         but only relevant pieces for tests"""
-        if isinstance(_request, commands.InfoDomain):
-            return self.mockInfoDomainCommands(_request, cleaned)
-        elif isinstance(_request, commands.InfoContact):
-            return self.mockInfoContactCommands(_request, cleaned)
-        elif isinstance(_request, commands.UpdateDomain):
-            return self.mockUpdateDomainCommands(_request, cleaned)
-        elif isinstance(_request, commands.CreateContact):
-            return self.mockCreateContactCommands(_request, cleaned)
-        elif isinstance(_request, commands.CreateHost):
-            return MagicMock(
-                res_data=[self.mockDataHostChange],
-                code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
-            )
-        elif isinstance(_request, commands.UpdateHost):
-            return MagicMock(
-                res_data=[self.mockDataHostChange],
-                code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
-            )
-        elif isinstance(_request, commands.DeleteHost):
-            return MagicMock(
-                res_data=[self.mockDataHostChange],
-                code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
-            )
-        elif (
-            isinstance(_request, commands.DeleteDomain)
-            and getattr(_request, "name", None) == "failDelete.gov"
-        ):
-            name = getattr(_request, "name", None)
-            fake_nameserver = "ns1.failDelete.gov"
-            if name in fake_nameserver:
-                raise RegistryError(
-                    code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
+
+        match type(_request):
+            case commands.InfoDomain:
+                return self.mockInfoDomainCommands(_request, cleaned)
+            case commands.InfoContact:
+                return self.mockInfoContactCommands(_request, cleaned)
+            case commands.CreateContact:
+                return self.mockCreateContactCommands(_request, cleaned)
+            case commands.UpdateDomain:
+                return self.mockUpdateDomainCommands(_request, cleaned)
+            case commands.CreateHost:
+                return MagicMock(
+                    res_data=[self.mockDataHostChange],
+                    code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
                 )
-        return MagicMock(res_data=[self.mockDataInfoHosts])
+            case commands.UpdateHost:
+                return MagicMock(
+                    res_data=[self.mockDataHostChange],
+                    code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
+                )
+            case commands.DeleteHost:
+                return MagicMock(
+                    res_data=[self.mockDataHostChange],
+                    code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
+                )
+            case commands.CheckDomain:
+                return self.mockCheckDomainCommand(_request, cleaned)
+            case commands.DeleteDomain:
+                return self.mockDeleteDomainCommands(_request, cleaned)
+            case _:
+                return MagicMock(res_data=[self.mockDataInfoHosts])
 
     def mockUpdateDomainCommands(self, _request, cleaned):
         if getattr(_request, "name", None) == "dnssec-invalid.gov":
@@ -833,6 +852,16 @@ def mockUpdateDomainCommands(self, _request, cleaned):
                 code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
             )
 
+    def mockDeleteDomainCommands(self, _request, cleaned):
+        if getattr(_request, "name", None) == "failDelete.gov":
+            name = getattr(_request, "name", None)
+            fake_nameserver = "ns1.failDelete.gov"
+            if name in fake_nameserver:
+                raise RegistryError(
+                    code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
+                )
+        return None
+
     def mockInfoDomainCommands(self, _request, cleaned):
         request_name = getattr(_request, "name", None)
 
diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py
--- a/src/registrar/tests/test_forms.py
+++ b/src/registrar/tests/test_forms.py
@@ -1,6 +1,6 @@
 """Test form validation requirements."""
 
-from django.test import TestCase
+from django.test import TestCase, RequestFactory
 
 from registrar.forms.application_wizard import (
     CurrentSitesForm,
@@ -16,9 +16,16 @@
     AboutYourOrganizationForm,
 )
 from registrar.forms.domain import ContactForm
+from registrar.tests.common import MockEppLib
+from django.contrib.auth import get_user_model
 
 
-class TestFormValidation(TestCase):
+class TestFormValidation(MockEppLib):
+    def setUp(self):
+        super().setUp()
+        self.user = get_user_model().objects.create(username="username")
+        self.factory = RequestFactory()
+
     def test_org_contact_zip_invalid(self):
         form = OrganizationContactForm(data={"zipcode": "nah"})
         self.assertEqual(