Repository: cisagov/manage.get.gov

Issue:
Change availability endpoint to not require a user to be logged in
### Issue description and context

Current api/v1/available requires a user to be logged in, even though the login required tag was removed already. Troubleshoot this and ensure that a non-logged in user can access the availability endpoint (api/v1/available)

### Acceptance criteria

- [ ] a user not logged in can access api/v1/available/<domain> and will see the response to whether the domain string passed is in fact available.

### Links to other issues

Blocks: #476


Discussion:
Now that we use a login-required middleware, we have to explicitly mark views as `login_not_required`. Here's an example of how that is done: https://github.com/cisagov/getgov/blob/16441092929e5486b44d6249798c794a25161bad/src/registrar/views/health.py#L8

Resolving patch:
diff --git a/src/api/views.py b/src/api/views.py
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -5,6 +5,8 @@
 
 import requests
 
+from login_required import login_not_required
+
 from cachetools.func import ttl_cache
 
 
@@ -23,6 +25,7 @@
     "invalid": "Enter a domain using only letters,"
     " numbers, or hyphens (though we don't recommend using hyphens).",
     "success": "That domain is available!",
+    "error": "Error finding domain availability.",
 }
 
 
@@ -50,22 +53,26 @@ def _domains():
     return domains
 
 
-def in_domains(domain):
-    """Return true if the given domain is in the domains list.
+def check_domain_available(domain):
+    """Return true if the given domain is available.
 
     The given domain is lowercased to match against the domains list. If the
     given domain doesn't end with .gov, ".gov" is added when looking for
     a match.
     """
     Domain = apps.get_model("registrar.Domain")
-    if domain.endswith(".gov"):
-        return Domain.available(domain)
-    else:
-        # domain search string doesn't end with .gov, add it on here
-        return Domain.available(domain + ".gov")
+    try:
+        if domain.endswith(".gov"):
+            return Domain.available(domain)
+        else:
+            # domain search string doesn't end with .gov, add it on here
+            return Domain.available(domain + ".gov")
+    except Exception:
+        return False
 
 
 @require_http_methods(["GET"])
+@login_not_required
 def available(request, domain=""):
     """Is a given domain available or not.
 
@@ -83,11 +90,16 @@ def available(request, domain=""):
             {"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
         )
     # a domain is available if it is NOT in the list of current domains
-    if in_domains(domain):
-        return JsonResponse(
-            {"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
-        )
-    else:
+    try:
+        if check_domain_available(domain):
+            return JsonResponse(
+                {"available": True, "message": DOMAIN_API_MESSAGES["success"]}
+            )
+        else:
+            return JsonResponse(
+                {"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
+            )
+    except Exception:
         return JsonResponse(
-            {"available": True, "message": DOMAIN_API_MESSAGES["success"]}
+            {"available": False, "message": DOMAIN_API_MESSAGES["error"]}
         )
diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py
--- a/src/registrar/models/utility/domain_helper.py
+++ b/src/registrar/models/utility/domain_helper.py
@@ -1,6 +1,6 @@
 import re
 
-from api.views import in_domains
+from api.views import check_domain_available
 from registrar.utility import errors
 
 
@@ -44,7 +44,7 @@ def validate(cls, domain: str | None, blank_ok=False) -> str:
             raise errors.ExtraDotsError()
         if not DomainHelper.string_could_be_domain(domain + ".gov"):
             raise ValueError()
-        if in_domains(domain):
+        if not check_domain_available(domain):
             raise errors.DomainUnavailableError()
         return domain
 


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
@@ -5,14 +5,13 @@
 from django.contrib.auth import get_user_model
 from django.test import RequestFactory
 
-from ..views import available, in_domains
+from ..views import available, check_domain_available
 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/"
@@ -37,10 +36,10 @@ def test_view_function(self):
         response_object = json.loads(response.content)
         self.assertIn("available", response_object)
 
-    def test_in_domains_makes_calls_(self):
+    def test_domain_available_makes_calls_(self):
         """Domain searches successfully make correct mock EPP calls"""
-        gsa_available = in_domains("gsa.gov")
-        igorville_available = in_domains("igorvilleremixed.gov")
+        gsa_available = check_domain_available("gsa.gov")
+        igorville_available = check_domain_available("igorville.gov")
 
         """Domain searches successfully make mock EPP calls"""
         self.mockedSendFunction.assert_has_calls(
@@ -53,29 +52,32 @@ def test_in_domains_makes_calls_(self):
                 ),
                 call(
                     commands.CheckDomain(
-                        ["igorvilleremixed.gov"],
+                        ["igorville.gov"],
                     ),
                     cleaned=True,
                 ),
             ]
         )
         """Domain searches return correct availability results"""
-        self.assertTrue(gsa_available)
-        self.assertFalse(igorville_available)
+        self.assertFalse(gsa_available)
+        self.assertTrue(igorville_available)
 
-    def test_in_domains_capitalized(self):
+    def test_domain_available_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"))
-
-    def test_in_domains_dotgov(self):
+        self.assertFalse(check_domain_available("gsa.gov"))
+        self.assertTrue(check_domain_available("igorville.gov"))
+        # input is lowercased so GSA.GOV should also not be available
+        self.assertFalse(check_domain_available("GSA.gov"))
+        # input is lowercased so IGORVILLE.GOV should also not be available
+        self.assertFalse(check_domain_available("IGORVILLE.gov"))
+
+    def test_domain_available_dotgov(self):
         """Domain searches work without trailing .gov"""
-        self.assertTrue(in_domains("gsa"))
+        self.assertFalse(check_domain_available("gsa"))
         # input is lowercased so GSA.GOV should be found
-        self.assertTrue(in_domains("GSA"))
-        # This domain should not have been registered
-        self.assertFalse(in_domains("igorvilleremixed"))
+        self.assertFalse(check_domain_available("GSA"))
+        # This domain should be available to register
+        self.assertTrue(check_domain_available("igorville"))
 
     def test_not_available_domain(self):
         """gsa.gov is not available"""
@@ -85,17 +87,17 @@ def test_not_available_domain(self):
         self.assertFalse(json.loads(response.content)["available"])
 
     def test_available_domain(self):
-        """igorvilleremixed.gov is still available"""
-        request = self.factory.get(API_BASE_PATH + "igorvilleremixed.gov")
+        """igorville.gov is still available"""
+        request = self.factory.get(API_BASE_PATH + "igorville.gov")
         request.user = self.user
-        response = available(request, domain="igorvilleremixed.gov")
+        response = available(request, domain="igorville.gov")
         self.assertTrue(json.loads(response.content)["available"])
 
     def test_available_domain_dotgov(self):
-        """igorvilleremixed.gov is still available even without the .gov suffix"""
-        request = self.factory.get(API_BASE_PATH + "igorvilleremixed")
+        """igorville.gov is still available even without the .gov suffix"""
+        request = self.factory.get(API_BASE_PATH + "igorville")
         request.user = self.user
-        response = available(request, domain="igorvilleremixed")
+        response = available(request, domain="igorville")
         self.assertTrue(json.loads(response.content)["available"])
 
     def test_error_handling(self):
@@ -105,10 +107,9 @@ 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"])
+        # domain set to raise error returns false for availability
+        error_domain_available = available(request, "errordomain.gov")
+        self.assertFalse(json.loads(error_domain_available.content)["available"])
 
 
 class AvailableAPITest(MockEppLib):
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
@@ -834,11 +834,17 @@ def _mockDomainName(self, _name, _avail=False):
 
     def mockCheckDomainCommand(self, _request, cleaned):
         if "gsa.gov" in getattr(_request, "names", None):
-            return self._mockDomainName("gsa.gov", True)
+            return self._mockDomainName("gsa.gov", False)
         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)
+            return self._mockDomainName("GSA.gov", False)
+        elif "igorville.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("igorvilleremixed.gov", True)
+        elif "top-level-agency.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("top-level-agency.gov", True)
+        elif "city.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("city.gov", True)
+        elif "city1.gov" in getattr(_request, "names", None):
+            return self._mockDomainName("city1.gov", True)
         elif "errordomain.gov" in getattr(_request, "names", None):
             raise RegistryError("Registry cannot find domain availability.")
         else:
diff --git a/src/registrar/tests/test_url_auth.py b/src/registrar/tests/test_url_auth.py
--- a/src/registrar/tests/test_url_auth.py
+++ b/src/registrar/tests/test_url_auth.py
@@ -110,12 +110,13 @@ class TestURLAuth(TestCase):
     # Note that the trailing slash is wobbly depending on how the URL was defined.
     IGNORE_URLS = [
         # These are the OIDC auth endpoints that always need
-        # to be public.
+        # to be public. Use the exact URLs that will be tested.
         "/openid/login/",
         "/openid/logout/",
         "/openid/callback",
         "/openid/callback/login/",
         "/openid/callback/logout/",
+        "/api/v1/available/whitehouse.gov",
     ]
 
     def assertURLIsProtectedByAuth(self, url):