diff --git a/src/api/tests/test_available.py b/src/api/tests/test_available.py index b9907c051..6f6e3775c 100644 --- a/src/api/tests/test_available.py +++ b/src/api/tests/test_available.py @@ -2,11 +2,14 @@ import json +from django.core.exceptions import BadRequest from django.contrib.auth import get_user_model from django.test import TestCase, RequestFactory from ..views import available, _domains, in_domains +API_BASE_PATH = "/api/v1/available/" + class AvailableViewTest(TestCase): @@ -17,7 +20,7 @@ class AvailableViewTest(TestCase): self.factory = RequestFactory() def test_view_function(self): - request = self.factory.get("/available/test.gov") + request = self.factory.get(API_BASE_PATH + "test.gov") request.user = self.user response = available(request, domain="test.gov") # has the right text in it @@ -27,7 +30,11 @@ class AvailableViewTest(TestCase): self.assertIn("available", response_object) def test_domain_list(self): - """Test the domain list that is returned.""" + """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 @@ -42,23 +49,44 @@ class AvailableViewTest(TestCase): self.assertTrue(in_domains("GSA.GOV")) # This domain should not have been registered self.assertFalse(in_domains("igorville.gov")) - # all the entries have dots - self.assertFalse(in_domains("gsa")) + + def test_in_domains_dotgov(self): + """Domain searches work without trailing .gov""" + self.assertTrue(in_domains("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("igorville")) def test_not_available_domain(self): """gsa.gov is not available""" - request = self.factory.get("/available/gsa.gov") + request = self.factory.get(API_BASE_PATH + "gsa.gov") request.user = self.user response = available(request, domain="gsa.gov") self.assertFalse(json.loads(response.content)["available"]) def test_available_domain(self): """igorville.gov is still available""" - request = self.factory.get("/available/igorville.gov") + request = self.factory.get(API_BASE_PATH + "igorville.gov") request.user = self.user response = available(request, domain="igorville.gov") self.assertTrue(json.loads(response.content)["available"]) + def test_available_domain_dotgov(self): + """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="igorville") + self.assertTrue(json.loads(response.content)["available"]) + + def test_error_handling(self): + """Calling with bad strings raises an error.""" + bad_string = "blah!;" + request = self.factory.get(API_BASE_PATH + bad_string) + request.user = self.user + with self.assertRaisesMessage(BadRequest, "Invalid"): + available(request, domain=bad_string) + class AvailableAPITest(TestCase): @@ -69,12 +97,17 @@ class AvailableAPITest(TestCase): def test_available_get(self): self.client.force_login(self.user) - response = self.client.get("/available/nonsense") + response = self.client.get(API_BASE_PATH + "nonsense") self.assertContains(response, "available") response_object = json.loads(response.content) self.assertIn("available", response_object) def test_available_post(self): """Cannot post to the /available/ API endpoint.""" - response = self.client.post("/available/nonsense") + response = self.client.post(API_BASE_PATH + "nonsense") self.assertEqual(response.status_code, 405) + + def test_available_bad_input(self): + self.client.force_login(self.user) + response = self.client.get(API_BASE_PATH + "blah!;") + self.assertEqual(response.status_code, 400) diff --git a/src/api/views.py b/src/api/views.py index 9b804d875..c25f98b97 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -1,6 +1,9 @@ """Internal API views""" +import re + +from django.core.exceptions import BadRequest from django.views.decorators.http import require_http_methods from django.http import JsonResponse @@ -13,6 +16,19 @@ from cachetools.func import ttl_cache DOMAIN_FILE_URL = ( "https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv" ) +# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't +# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters +DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?/", application_wizard, name=APPLICATION_URL_NAME), - path("available/", available, name="available"), + path("api/v1/available/", available, name="available"), ] if not settings.DEBUG: