From 9a20863e2cf3b19ae3fecd372bbea489b7d8b31b Mon Sep 17 00:00:00 2001 From: Carson Davis Date: Fri, 22 Nov 2024 22:13:53 -0600 Subject: [PATCH] add api tests for EJ --- environmental_justice/tests.py | 3 - environmental_justice/tests/conftest.py | 30 +++++ environmental_justice/tests/factories.py | 28 ++++ environmental_justice/tests/test_views.py | 153 ++++++++++++++++++++++ 4 files changed, 211 insertions(+), 3 deletions(-) delete mode 100644 environmental_justice/tests.py create mode 100644 environmental_justice/tests/conftest.py create mode 100644 environmental_justice/tests/factories.py create mode 100644 environmental_justice/tests/test_views.py diff --git a/environmental_justice/tests.py b/environmental_justice/tests.py deleted file mode 100644 index 9a30df3b..00000000 --- a/environmental_justice/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase # noqa - -# Create your tests here. diff --git a/environmental_justice/tests/conftest.py b/environmental_justice/tests/conftest.py new file mode 100644 index 00000000..d8b53c9a --- /dev/null +++ b/environmental_justice/tests/conftest.py @@ -0,0 +1,30 @@ +import pytest +from django.urls import include, path +from rest_framework.routers import DefaultRouter +from rest_framework.test import APIClient + +from environmental_justice.views import EnvironmentalJusticeRowViewSet + +# Create router and register our viewset +router = DefaultRouter() +router.register(r"environmental-justice", EnvironmentalJusticeRowViewSet) + +# Create temporary urlpatterns for testing +urlpatterns = [ + path("api/", include(router.urls)), +] + + +# Override default URL conf for testing +@pytest.fixture +def client(): + """Return a Django REST framework API client""" + return APIClient() + + +@pytest.fixture(autouse=True) +def setup_urls(): + """Setup URLs for testing""" + from django.conf import settings + + settings.ROOT_URLCONF = __name__ diff --git a/environmental_justice/tests/factories.py b/environmental_justice/tests/factories.py new file mode 100644 index 00000000..42d05735 --- /dev/null +++ b/environmental_justice/tests/factories.py @@ -0,0 +1,28 @@ +import factory +from factory.django import DjangoModelFactory + +from environmental_justice.models import EnvironmentalJusticeRow + + +class EnvironmentalJusticeRowFactory(DjangoModelFactory): + class Meta: + model = EnvironmentalJusticeRow + + dataset = factory.Sequence(lambda n: f"dataset_{n}") + description = factory.Faker("sentence") + description_simplified = factory.Faker("sentence") + indicators = factory.Faker("sentence") + intended_use = factory.Faker("sentence") + latency = factory.Faker("word") + limitations = factory.Faker("sentence") + project = factory.Faker("word") + source_link = factory.Faker("url") + strengths = factory.Faker("sentence") + format = factory.Faker("file_extension") + geographic_coverage = factory.Faker("country") + data_visualization = factory.Faker("sentence") + spatial_resolution = factory.Faker("word") + temporal_extent = factory.Faker("date") + temporal_resolution = factory.Faker("word") + sde_link = factory.Faker("url") + data_source = EnvironmentalJusticeRow.DataSourceChoices.SPREADSHEET diff --git a/environmental_justice/tests/test_views.py b/environmental_justice/tests/test_views.py new file mode 100644 index 00000000..1632d45b --- /dev/null +++ b/environmental_justice/tests/test_views.py @@ -0,0 +1,153 @@ +# docker-compose -f local.yml run --rm django pytest environmental_justice/tests/test_views.py +import pytest +from rest_framework import status + +from environmental_justice.models import EnvironmentalJusticeRow +from environmental_justice.tests.factories import EnvironmentalJusticeRowFactory + + +@pytest.mark.django_db +class TestEnvironmentalJusticeRowViewSet: + """Test suite for the EnvironmentalJusticeRow API endpoints""" + + def setup_method(self): + """Setup URL for API endpoint""" + self.url = "/api/environmental-justice/" + + def test_empty_database_returns_empty_list(self, client): + """Should return empty list when no records exist""" + response = client.get(self.url) + assert response.status_code == status.HTTP_200_OK + assert response.json()["results"] == [] + assert response.json()["count"] == 0 + + def test_single_source_filtering(self, client): + """Should return records only from requested data source""" + # Create records for each data source + spreadsheet_record = EnvironmentalJusticeRowFactory( + dataset="test_dataset", data_source=EnvironmentalJusticeRow.DataSourceChoices.SPREADSHEET + ) + ml_prod_record = EnvironmentalJusticeRowFactory( + dataset="another_dataset", data_source=EnvironmentalJusticeRow.DataSourceChoices.ML_PRODUCTION + ) + ml_test_record = EnvironmentalJusticeRowFactory( + dataset="test_dataset_3", data_source=EnvironmentalJusticeRow.DataSourceChoices.ML_TESTING + ) + + # Test spreadsheet filter + response = client.get(f"{self.url}?data_source=spreadsheet") + assert response.status_code == status.HTTP_200_OK + data = response.json()["results"] + assert len(data) == 1 + assert data[0]["dataset"] == spreadsheet_record.dataset + + # Test ml_production filter + response = client.get(f"{self.url}?data_source=ml_production") + assert response.status_code == status.HTTP_200_OK + data = response.json()["results"] + assert len(data) == 1 + assert data[0]["dataset"] == ml_prod_record.dataset + + # Test ml_testing filter + response = client.get(f"{self.url}?data_source=ml_testing") + assert response.status_code == status.HTTP_200_OK + data = response.json()["results"] + assert len(data) == 1 + assert data[0]["dataset"] == ml_test_record.dataset + + def test_combined_data_precedence(self, client): + """ + Should return combined data with spreadsheet taking precedence over ml_production + for matching datasets + """ + # Create spreadsheet record + EnvironmentalJusticeRowFactory( + dataset="common_dataset", + description="spreadsheet version", + data_source=EnvironmentalJusticeRow.DataSourceChoices.SPREADSHEET, + ) + + # Create ML production record with same dataset + EnvironmentalJusticeRowFactory( + dataset="common_dataset", + description="ml version", + data_source=EnvironmentalJusticeRow.DataSourceChoices.ML_PRODUCTION, + ) + + # Create unique ML production record + EnvironmentalJusticeRowFactory( + dataset="unique_ml_dataset", data_source=EnvironmentalJusticeRow.DataSourceChoices.ML_PRODUCTION + ) + + # Test combined view (default) + response = client.get(self.url) + assert response.status_code == status.HTTP_200_OK + data = response.json()["results"] + assert len(data) == 2 # Should only return 2 records (not 3) + + # Verify correct records are returned + datasets = [record["dataset"] for record in data] + assert "common_dataset" in datasets + assert "unique_ml_dataset" in datasets + + # Verify precedence - should get spreadsheet version of common dataset + common_record = next(r for r in data if r["dataset"] == "common_dataset") + assert common_record["description"] == "spreadsheet version" + + def test_combined_explicit_parameter(self, client): + """Should handle explicit 'combined' parameter same as default""" + EnvironmentalJusticeRowFactory(data_source=EnvironmentalJusticeRow.DataSourceChoices.SPREADSHEET) + EnvironmentalJusticeRowFactory( + dataset="unique_ml_dataset", # Ensure different dataset + data_source=EnvironmentalJusticeRow.DataSourceChoices.ML_PRODUCTION, + ) + + # Compare default and explicit combined responses + default_response = client.get(self.url) + combined_response = client.get(f"{self.url}?data_source=combined") + + assert default_response.status_code == status.HTTP_200_OK + assert combined_response.status_code == status.HTTP_200_OK + assert default_response.json()["results"] == combined_response.json()["results"] + + def test_invalid_data_source(self, client): + """Should return 400 error for invalid data_source parameter""" + response = client.get(f"{self.url}?data_source=invalid") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "Invalid data_source" in str(response.json()) + + def test_sorting_in_combined_view(self, client): + """Should return combined results sorted by dataset name""" + # Create records in non-alphabetical order + EnvironmentalJusticeRowFactory( + dataset="zebra_dataset", data_source=EnvironmentalJusticeRow.DataSourceChoices.SPREADSHEET + ) + EnvironmentalJusticeRowFactory( + dataset="alpha_dataset", data_source=EnvironmentalJusticeRow.DataSourceChoices.ML_PRODUCTION + ) + + response = client.get(self.url) + assert response.status_code == status.HTTP_200_OK + data = response.json()["results"] + + # Verify sorting + datasets = [record["dataset"] for record in data] + assert datasets == sorted(datasets) + + def test_http_methods_allowed(self, client): + """Should only allow GET requests""" + # Test GET (should work) + get_response = client.get(self.url) + assert get_response.status_code == status.HTTP_200_OK + + # Test POST (should fail) + post_response = client.post(self.url, {}) + assert post_response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + + # Test PUT (should fail) + put_response = client.put(self.url, {}) + assert put_response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED + + # Test DELETE (should fail) + delete_response = client.delete(self.url) + assert delete_response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED