diff --git a/api/data_workspace/v1/tests/test_staticdata_views.py b/api/data_workspace/v1/tests/test_staticdata_views.py index 241efa0208..286f8eaa6c 100644 --- a/api/data_workspace/v1/tests/test_staticdata_views.py +++ b/api/data_workspace/v1/tests/test_staticdata_views.py @@ -7,7 +7,7 @@ class DataWorkspaceTests(DataTestClient): def test_control_list_entries(self): url = reverse("data_workspace:v1:dw-control-list-entries-list") - expected_fields = ("id", "rating", "text", "category", "controlled", "parent") + expected_fields = ("id", "rating", "text", "category", "controlled", "selectable_for_assessment", "parent") response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/api/staticdata/control_list_entries/factories.py b/api/staticdata/control_list_entries/factories.py index ff4325ac83..f8baa1e1fd 100644 --- a/api/staticdata/control_list_entries/factories.py +++ b/api/staticdata/control_list_entries/factories.py @@ -8,6 +8,7 @@ class ControlListEntriesFactory(factory.django.DjangoModelFactory): parent = None category = "test-list" controlled = True + selectable_for_assessment = True class Meta: model = models.ControlListEntry diff --git a/api/staticdata/control_list_entries/migrations/0006_controllistentry_selectable_for_assessment.py b/api/staticdata/control_list_entries/migrations/0006_controllistentry_selectable_for_assessment.py new file mode 100644 index 0000000000..6c73d3ee63 --- /dev/null +++ b/api/staticdata/control_list_entries/migrations/0006_controllistentry_selectable_for_assessment.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-09-13 13:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("control_list_entries", "0005_adds_5D001e"), + ] + + operations = [ + migrations.AddField( + model_name="controllistentry", + name="selectable_for_assessment", + field=models.BooleanField(default=True), + ), + ] diff --git a/api/staticdata/control_list_entries/models.py b/api/staticdata/control_list_entries/models.py index f05325d916..e22a999448 100755 --- a/api/staticdata/control_list_entries/models.py +++ b/api/staticdata/control_list_entries/models.py @@ -10,6 +10,7 @@ class ControlListEntry(models.Model): parent = models.ForeignKey("self", related_name="children", default=None, null=True, on_delete=models.CASCADE) category = models.CharField(max_length=100, default="") controlled = models.BooleanField(default=True) + selectable_for_assessment = models.BooleanField(default=True) class Meta: db_table = "control_list_entry" diff --git a/api/staticdata/control_list_entries/tests/__init__.py b/api/staticdata/control_list_entries/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/staticdata/control_list_entries/tests/test_views.py b/api/staticdata/control_list_entries/tests/test_views.py new file mode 100644 index 0000000000..1dc7c3ef0f --- /dev/null +++ b/api/staticdata/control_list_entries/tests/test_views.py @@ -0,0 +1,90 @@ +from rest_framework.reverse import reverse + +from api.staticdata.control_list_entries.models import ControlListEntry +from api.staticdata.control_list_entries.factories import ControlListEntriesFactory +from test_helpers.clients import DataTestClient + + +class ControlListEntriesListTests(DataTestClient): + """ + Most other view tests are in api/staticdata/control_list_entries/test.py + """ + + def setUp(self): + super().setUp() + self.url = reverse("staticdata:control_list_entries:control_list_entries") + + def test_gov_user_control_list_entries_list_ignores_unselectable_cles(self): + cles_count_model = ControlListEntry.objects.all().count() + + # Assert that we have at least 1 CLE returned by the db manager + self.assertTrue(cles_count_model > 0) + + response = self.client.get(self.url, **self.gov_headers) + cles_data = response.json().get("control_list_entries") + cles_count_data = len(cles_data) + + # Assert that we have at least 1 CLE returned by the view + self.assertTrue(cles_count_data > 0) + + # Create a CLE with selectable_for_assessment=False + unselectable_cle = ControlListEntriesFactory(rating="rating123", text="text", selectable_for_assessment=False) + + # Assert that the object was created successfully + self.assertFalse(unselectable_cle.selectable_for_assessment) + self.assertTrue( + ControlListEntry.objects.filter(rating="rating123", selectable_for_assessment=False).count() == 1 + ) + + updated_cles_count_model = ControlListEntry.objects.all().count() + + # Assert that the count returned by the db manager has increased by 1 + self.assertTrue(updated_cles_count_model == cles_count_model + 1) + + response = self.client.get(self.url, **self.gov_headers) + updated_cles_data = response.json().get("control_list_entries") + updated_cles_count_data = len(updated_cles_data) + + # Assert that the count returned by the view is unchanged + self.assertTrue(updated_cles_count_data == cles_count_data) + + # Assert that the data returned by the view does not contain the unselectable CLE + self.assertNotIn("rating123", [cle["rating"] for cle in updated_cles_data]) + + def test_gov_user_control_list_entries_list_includes_unselectable_cles_if_include_unselectable_is_true(self): + url = reverse("staticdata:control_list_entries:control_list_entries") + "?include_unselectable=True" + cles_count_model = ControlListEntry.objects.all().count() + + # Assert that we have at least 1 CLE returned by the db manager + self.assertTrue(cles_count_model > 0) + + response = self.client.get(url, **self.gov_headers) + cles_data = response.json().get("control_list_entries") + cles_count_data = len(cles_data) + + # Assert that we have at least 1 CLE returned by the view + self.assertTrue(cles_count_data > 0) + + # Create a CLE with selectable_for_assessment=False + unselectable_cle = ControlListEntriesFactory(rating="rating123", text="text", selectable_for_assessment=False) + + # Assert that the object was created successfully + self.assertFalse(unselectable_cle.selectable_for_assessment) + self.assertTrue( + ControlListEntry.objects.filter(rating="rating123", selectable_for_assessment=False).count() == 1 + ) + + updated_cles_count_model = ControlListEntry.objects.all().count() + + # Assert that the count returned by the db manager has increased by 1 + self.assertTrue(updated_cles_count_model == cles_count_model + 1) + + response = self.client.get(url, **self.gov_headers) + updated_cles_data = response.json().get("control_list_entries") + updated_cles_count_data = len(updated_cles_data) + + # Assert that the count returned by the view has increased by 1 + self.assertTrue(updated_cles_count_data == cles_count_data + 1) + + # Assert that the data returned by the view contains the unselectable CLE + self.assertIn("rating123", [cle["rating"] for cle in updated_cles_data]) diff --git a/api/staticdata/control_list_entries/views.py b/api/staticdata/control_list_entries/views.py index b19d86171d..182312d3a8 100755 --- a/api/staticdata/control_list_entries/views.py +++ b/api/staticdata/control_list_entries/views.py @@ -3,7 +3,7 @@ from rest_framework.decorators import permission_classes from rest_framework.views import APIView -from api.core.authentication import SharedAuthentication +from api.core.authentication import GovAuthentication, SharedAuthentication from api.staticdata.control_list_entries.helpers import get_control_list_entry, convert_control_list_entries_to_tree from api.staticdata.control_list_entries.models import ControlListEntry from api.staticdata.control_list_entries.serializers import ControlListEntrySerializerWithLinks @@ -11,17 +11,22 @@ @permission_classes((permissions.AllowAny,)) class ControlListEntriesList(APIView): - authentication_classes = (SharedAuthentication,) + authentication_classes = (GovAuthentication,) + + def get_queryset(self, include_unselectable=False): + if include_unselectable: + return ControlListEntry.objects.filter(controlled=True) - def get_queryset(self): - return ControlListEntry.objects.filter(controlled=True) + return ControlListEntry.objects.filter(controlled=True, selectable_for_assessment=True) def get(self, request): """ Returns list of all Control List Entries """ - queryset = self.get_queryset() + include_unselectable = request.GET.get("include_unselectable", False) + + queryset = self.get_queryset(include_unselectable=include_unselectable) if request.GET.get("group", False): return JsonResponse(data={"control_list_entries": convert_control_list_entries_to_tree(queryset.values())}) diff --git a/api/staticdata/exporter/__init__.py b/api/staticdata/exporter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/staticdata/exporter/control_list_entries/__init__.py b/api/staticdata/exporter/control_list_entries/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/staticdata/exporter/control_list_entries/tests/__init__.py b/api/staticdata/exporter/control_list_entries/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/staticdata/exporter/control_list_entries/tests/test_views.py b/api/staticdata/exporter/control_list_entries/tests/test_views.py index ef7770e185..93c476e5bb 100644 --- a/api/staticdata/exporter/control_list_entries/tests/test_views.py +++ b/api/staticdata/exporter/control_list_entries/tests/test_views.py @@ -46,3 +46,78 @@ def test_list_view_success_exact_response(self): {"rating": cle_3.rating, "text": cle_3.text}, ], ) + + def test_list_view_ignores_unselectable_cles_by_default(self): + cles_count_model = ControlListEntry.objects.all().count() + + # Assert that we have at least 1 CLE returned by the db manager + self.assertTrue(cles_count_model > 0) + + response = self.client.get(self.url, **self.exporter_headers) + cles_data = response.json() + cles_count_data = len(cles_data) + + # Assert that we have at least 1 CLE returned by the view + self.assertTrue(cles_count_data > 0) + + # Create a CLE with selectable_for_assessment=False + unselectable_cle = ControlListEntriesFactory(rating="rating123", text="text", selectable_for_assessment=False) + + # Assert that the object was created successfully + self.assertFalse(unselectable_cle.selectable_for_assessment) + self.assertTrue( + ControlListEntry.objects.filter(rating="rating123", selectable_for_assessment=False).count() == 1 + ) + + updated_cles_count_model = ControlListEntry.objects.all().count() + + # Assert that the count returned by the db manager has increased by 1 + self.assertTrue(updated_cles_count_model == cles_count_model + 1) + + response = self.client.get(self.url, **self.exporter_headers) + updated_cles_data = response.json() + updated_cles_count_data = len(updated_cles_data) + + # Assert that the count returned by the view is unchanged + self.assertTrue(updated_cles_count_data == cles_count_data) + + # Assert that the data returned by the view does not contain the unselectable CLE + self.assertNotIn("rating123", [cle["rating"] for cle in updated_cles_data]) + + def test_list_view_includes_unselectable_cles_if_include_unselectable_is_true(self): + url = reverse("exporter_staticdata:control_list_entries:control_list_entries") + "?include_unselectable=True" + cles_count_model = ControlListEntry.objects.all().count() + + # Assert that we have at least 1 CLE returned by the db manager + self.assertTrue(cles_count_model > 0) + + response = self.client.get(url, **self.exporter_headers) + cles_data = response.json() + cles_count_data = len(cles_data) + + # Assert that we have at least 1 CLE returned by the view + self.assertTrue(cles_count_data > 0) + + # Create a CLE with selectable_for_assessment=False + unselectable_cle = ControlListEntriesFactory(rating="rating123", text="text", selectable_for_assessment=False) + + # Assert that the object was created successfully + self.assertFalse(unselectable_cle.selectable_for_assessment) + self.assertTrue( + ControlListEntry.objects.filter(rating="rating123", selectable_for_assessment=False).count() == 1 + ) + + updated_cles_count_model = ControlListEntry.objects.all().count() + + # Assert that the count returned by the db manager has increased by 1 + self.assertTrue(updated_cles_count_model == cles_count_model + 1) + + response = self.client.get(url, **self.exporter_headers) + updated_cles_data = response.json() + updated_cles_count_data = len(updated_cles_data) + + # Assert that the count returned by the view has increased by 1 + self.assertTrue(updated_cles_count_data == cles_count_data + 1) + + # Assert that the data returned by the view contains the unselectable CLE + self.assertIn("rating123", [cle["rating"] for cle in updated_cles_data]) diff --git a/api/staticdata/exporter/control_list_entries/views.py b/api/staticdata/exporter/control_list_entries/views.py index d3097850a7..b95bca5801 100644 --- a/api/staticdata/exporter/control_list_entries/views.py +++ b/api/staticdata/exporter/control_list_entries/views.py @@ -9,4 +9,10 @@ class ControlListEntriesList(generics.ListAPIView): authentication_classes = (ExporterAuthentication,) pagination_class = None serializer_class = ControlListEntriesListSerializer - queryset = ControlListEntry.objects.filter(controlled=True) + + def get_queryset(self): + include_unselectable = self.request.GET.get("include_unselectable", False) + if include_unselectable: + return ControlListEntry.objects.filter(controlled=True) + + return ControlListEntry.objects.filter(controlled=True, selectable_for_assessment=True)