"""The processing-batch API. The portal screens have their own tests; these pin the API boundary that used to be missing. ``ProcessingBatchViewSet`` was open to any authenticated user and the write serializer accepted ``status``, so a buyer could PATCH a failed batch to ``delivered``. The write rules mirror ``apps/ops/forms.py``: a QC status is set by a QC result, and a failed batch is sticky. """ from django.contrib.auth import get_user_model from django.test import TestCase from rest_framework.test import APIClient from .models import ProcessingBatch User = get_user_model() class ProcessingBatchAPITests(TestCase): def setUp(self): self.ops = User.objects.create_user( email="batch-api-ops@ixg.local", password="pass1234", role="ops", full_name="Batch API Ops", is_staff=True, ) self.buyer = User.objects.create_user( email="batch-api-buyer@ixg.local", password="pass1234", role="buyer", full_name="Batch API Buyer", is_staff=True, ) self.failed = ProcessingBatch.objects.create( batch_reference="IXG-BATCH-API-001", status=ProcessingBatch.Status.QC_FAIL, ) self.client = APIClient() def test_a_buyer_cannot_patch_a_batch(self): self.client.force_authenticate(user=self.buyer) response = self.client.patch( f"/api/batches/{self.failed.id}/", {"status": "delivered"}, format="json" ) self.assertEqual(response.status_code, 403) self.failed.refresh_from_db() self.assertEqual(self.failed.status, ProcessingBatch.Status.QC_FAIL) def test_a_buyer_cannot_read_the_batch_api(self): """The buyer portal reads batches through the ORM, not this API, so the API needs no buyer read and is closed to one.""" self.client.force_authenticate(user=self.buyer) self.assertEqual(self.client.get("/api/batches/").status_code, 403) def test_ops_can_read_the_batch_api(self): self.client.force_authenticate(user=self.ops) self.assertEqual(self.client.get("/api/batches/").status_code, 200) def test_a_qc_status_cannot_be_hand_set(self): self.client.force_authenticate(user=self.ops) journey = ProcessingBatch.objects.create( batch_reference="IXG-BATCH-API-002", status=ProcessingBatch.Status.HARVESTED, ) response = self.client.patch( f"/api/batches/{journey.id}/", {"status": "qc_pass"}, format="json" ) self.assertEqual(response.status_code, 400) journey.refresh_from_db() self.assertEqual(journey.status, ProcessingBatch.Status.HARVESTED) def test_a_batch_cannot_be_created_with_a_qc_status(self): self.client.force_authenticate(user=self.ops) response = self.client.post( "/api/batches/", {"batch_reference": "IXG-BATCH-API-003", "status": "qc_pass"}, format="json", ) self.assertEqual(response.status_code, 400) self.assertFalse( ProcessingBatch.objects.filter(batch_reference="IXG-BATCH-API-003").exists() ) def test_a_failed_batch_cannot_be_moved_out_of_qc_fail(self): self.client.force_authenticate(user=self.ops) response = self.client.patch( f"/api/batches/{self.failed.id}/", {"status": "delivered"}, format="json" ) self.assertEqual(response.status_code, 400) self.failed.refresh_from_db() self.assertEqual(self.failed.status, ProcessingBatch.Status.QC_FAIL) def test_a_failed_batch_can_be_saved_with_its_own_status(self): self.client.force_authenticate(user=self.ops) response = self.client.patch( f"/api/batches/{self.failed.id}/", {"status": "qc_fail"}, format="json" ) self.assertEqual(response.status_code, 200)