ixg_platform/apps/shipments/tests.py
ixgadmin 5b6c4c48e4 Test the shipment screen, its API rules and the date inputs
Cover access on GET and POST for a buyer, each required field when blank, an ETD
after the ETA, a batch that already has a shipment (queried, not blocked), a
status change, and an edit updating in place. Add the shipment routes to both
OPS_ROUTES sweeps so the detail and edit screens render clean markup, and assert
the native date inputs carry ISO values — the defect that left the intake and
batch edit dates blank.
2026-09-17 16:03:14 +00:00

98 lines
3.8 KiB
Python

"""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)