"""The shipment API. The portal form and the API must agree: the fields the buyer's page reads are required, and a departure after the arrival is refused. The permission boundary itself is swept in ``apps/core/tests.py``; these pin the serializer rules so the API cannot accept what the ops form refuses. """ from django.contrib.auth import get_user_model from django.test import TestCase from rest_framework.test import APIClient from apps.batches.models import ProcessingBatch from .models import Shipment User = get_user_model() class ShipmentAPITests(TestCase): def setUp(self): self.ops = User.objects.create_user( email="ship-api-ops@ixg.local", password="pass1234", role="ops", full_name="Shipment API Ops", is_staff=True, ) self.buyer = User.objects.create_user( email="ship-api-buyer@ixg.local", password="pass1234", role="buyer", full_name="Shipment API Buyer", is_staff=True, ) self.batch = ProcessingBatch.objects.create( batch_reference="IXG-BATCH-API-SHIP-001", status="ready_to_ship", ) self.shipment = Shipment.objects.create( batch=self.batch, status=Shipment.Status.BOOKED, vessel_name="MV API", container_ref="IXGU0000001", etd_nigeria="2026-09-15", eta_uk="2026-10-15", ) self.client = APIClient() def payload(self, **overrides): data = { "batch": str(self.batch.pk), "status": "booked", "vessel_name": "MV API", "container_ref": "IXGU0000002", "etd_nigeria": "2026-09-20", "eta_uk": "2026-10-20", } data.update(overrides) return data def test_a_buyer_cannot_read_or_write_shipments(self): self.client.force_authenticate(user=self.buyer) self.assertEqual(self.client.get("/api/shipments/").status_code, 403) self.assertEqual( self.client.post("/api/shipments/", self.payload(), format="json").status_code, 403, ) def test_ops_can_create_a_shipment(self): self.client.force_authenticate(user=self.ops) response = self.client.post("/api/shipments/", self.payload(), format="json") self.assertEqual(response.status_code, 201) self.assertEqual(Shipment.objects.count(), 2) def test_each_required_field_is_refused_when_blank(self): self.client.force_authenticate(user=self.ops) for field in ( "status", "vessel_name", "container_ref", "etd_nigeria", "eta_uk", ): with self.subTest(field=field): response = self.client.post( "/api/shipments/", self.payload(**{field: ""}), format="json" ) self.assertEqual(response.status_code, 400) self.assertIn(field, response.json()) self.assertEqual(Shipment.objects.count(), 1) def test_a_departure_after_the_arrival_is_refused(self): self.client.force_authenticate(user=self.ops) response = self.client.post( "/api/shipments/", self.payload(etd_nigeria="2026-11-01", eta_uk="2026-10-01"), format="json", ) self.assertEqual(response.status_code, 400) self.assertIn("eta_uk", response.json()) self.assertEqual(Shipment.objects.count(), 1) def test_a_partial_status_change_does_not_require_the_other_fields(self): self.client.force_authenticate(user=self.ops) response = self.client.patch( f"/api/shipments/{self.shipment.pk}/", {"status": "in_transit"}, format="json", ) self.assertEqual(response.status_code, 200) self.shipment.refresh_from_db() self.assertEqual(self.shipment.status, Shipment.Status.IN_TRANSIT)