from django.test import TestCase, Client from django.contrib.auth.models import Permission from knox.models import AuthToken from django.utils import timezone from authentication.models import ExtendedUser from inventory.models import Event, Item, Container from tickets.models import IssueThread from shipments.models import Shipment class ShipmentApiTest(TestCase): def setUp(self): super().setUp() # Create a test user with all permissions self.user = ExtendedUser.objects.create_user('testuser', 'test@example.com', 'test') self.user.user_permissions.add(*Permission.objects.all()) self.user.save() # Create authentication token self.token = AuthToken.objects.create(user=self.user) self.client = Client(headers={'Authorization': 'Token ' + self.token[1]}) # Create test event and container self.event = Event.objects.create(slug='test-event', name='Test Event') self.container = Container.objects.create(name='Test Container') # Create test items with container self.item1 = Item.objects.create(description='Test Item 1', event=self.event, container=self.container) self.item2 = Item.objects.create(description='Test Item 2', event=self.event, container=self.container) # Create test ticket self.ticket = IssueThread.objects.create(name='Test Ticket') def test_list_shipments_empty(self): """Test listing shipments when none exist""" response = self.client.get('/api/2/shipments/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), []) def test_create_shipment(self): """Test creating a new shipment""" data = { 'recipient_name': 'John Smith', 'street_address': '123 Oxford Street', 'city': 'London', 'state_province': 'Greater London', 'postal_code': 'W1D 2HG', 'country': 'United Kingdom', 'related_items': [], 'related_tickets': [] } response = self.client.post('/api/2/shipments/', data, content_type='application/json') self.assertEqual(response.status_code, 201) self.assertEqual(response.json()['recipient_name'], 'John Smith') self.assertEqual(response.json()['state'], Shipment.CREATED) self.assertEqual(response.json()['review_requirement'], None) # Retrieve the created shipment and verify its details shipment_id = response.json()['id'] get_response = self.client.get(f'/api/2/shipments/{shipment_id}/') self.assertEqual(get_response.status_code, 200) self.assertEqual(get_response.json()['id'], shipment_id) self.assertEqual(get_response.json()['recipient_name'], 'John Smith') self.assertEqual(get_response.json()['street_address'], '123 Oxford Street') self.assertEqual(get_response.json()['city'], 'London') self.assertEqual(get_response.json()['state_province'], 'Greater London') self.assertEqual(get_response.json()['postal_code'], 'W1D 2HG') self.assertEqual(get_response.json()['country'], 'United Kingdom') self.assertEqual(get_response.json()['state'], Shipment.CREATED) self.assertEqual(get_response.json()['review_requirement'], None) def test_get_shipment(self): """Test retrieving a specific shipment""" # Create a shipment first shipment = Shipment.objects.create( recipient_name='Maria Garcia', street_address='Calle Gran Via 28', city='Madrid', state_province='Madrid', postal_code='28013', country='Spain' ) response = self.client.get(f'/api/2/shipments/{shipment.id}/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['id'], shipment.id) self.assertEqual(response.json()['recipient_name'], 'Maria Garcia') def test_update_shipment(self): """Test updating a shipment""" # Create a shipment first shipment = Shipment.objects.create( recipient_name='Hans Mueller', street_address='Kurfürstendamm 216', city='Berlin', state_province='Berlin', postal_code='10719', country='Germany' ) data = { 'recipient_name': 'Hans-Jürgen Mueller', 'address_supplements': 'Apartment 4B' } response = self.client.patch(f'/api/2/shipments/{shipment.id}/', data, content_type='application/json') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['recipient_name'], 'Hans-Jürgen Mueller') self.assertEqual(response.json()['address_supplements'], 'Apartment 4B') def test_delete_shipment(self): """Test deleting a shipment""" # Create a shipment first shipment = Shipment.objects.create( recipient_name='Sophie Dubois', street_address='15 Rue de Rivoli', city='Paris', state_province='Île-de-France', postal_code='75001', country='France' ) response = self.client.delete(f'/api/2/shipments/{shipment.id}/') self.assertEqual(response.status_code, 204) # Verify it's soft deleted - use all_objects manager to see deleted items self.assertTrue(Shipment.all_objects.filter(id=shipment.id).exists()) # Check if it's marked as deleted shipment.refresh_from_db() self.assertIsNotNone(shipment.deleted_at) def test_add_items(self): """Test adding items to a shipment""" # Create a shipment first shipment = Shipment.objects.create( recipient_name='Alessandro Romano', street_address='Via del Corso 123', city='Rome', state_province='Lazio', postal_code='00186', country='Italy' ) data = { 'item_ids': [self.item1.id, self.item2.id], 'review_requirement': Shipment.REVIEW_ITEMS } response = self.client.post(f'/api/2/shipments/{shipment.id}/add_items/', data, content_type='application/json') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['state'], Shipment.PENDING_REVIEW) self.assertEqual(response.json()['review_requirement'], Shipment.REVIEW_ITEMS) # Verify items were added shipment.refresh_from_db() self.assertEqual(shipment.related_items.count(), 2) def test_approve_shipment(self): """Test approving a shipment""" # Create a shipment and add items shipment = Shipment.objects.create( recipient_name='Yuki Tanaka', street_address='1-1-1 Shibuya', city='Tokyo', state_province='Tokyo', postal_code='150-0002', country='Japan' ) shipment.related_items.add(self.item1) shipment.review_requirement = Shipment.REVIEW_ITEMS shipment.state = Shipment.PENDING_REVIEW shipment.save() response = self.client.post(f'/api/2/shipments/{shipment.id}/approve/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['state'], Shipment.READY_FOR_SHIPPING) def test_reject_shipment(self): """Test rejecting a shipment""" # Create a shipment and add items shipment = Shipment.objects.create( recipient_name='Carlos Rodriguez', street_address='Avenida Insurgentes Sur 1602', city='Mexico City', state_province='CDMX', postal_code='03940', country='Mexico' ) shipment.related_items.add(self.item1) shipment.review_requirement = Shipment.REVIEW_ITEMS shipment.state = Shipment.PENDING_REVIEW shipment.save() response = self.client.post(f'/api/2/shipments/{shipment.id}/reject/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['state'], Shipment.REJECTED) def test_mark_shipped(self): """Test marking a shipment as shipped""" # Create a shipment and set it to READY_FOR_SHIPPING shipment = Shipment.objects.create( recipient_name='Anna Kowalski', street_address='ul. Marszałkowska 126', city='Warsaw', state_province='Mazowieckie', postal_code='00-008', country='Poland', state=Shipment.READY_FOR_SHIPPING ) shipment.related_items.add(self.item1) response = self.client.post(f'/api/2/shipments/{shipment.id}/mark_shipped/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['state'], Shipment.SHIPPED) self.assertIsNotNone(response.json()['shipped_at']) def test_mark_completed(self): """Test marking a shipment as completed""" # Create a shipment and set it to SHIPPED shipment = Shipment.objects.create( recipient_name='Lars Andersen', street_address='Strøget 1', city='Copenhagen', state_province='Capital Region', postal_code='1095', country='Denmark', state=Shipment.SHIPPED, shipped_at=timezone.now() ) shipment.related_items.add(self.item1) response = self.client.post(f'/api/2/shipments/{shipment.id}/mark_completed/') self.assertEqual(response.status_code, 200) self.assertEqual(response.json()['state'], Shipment.COMPLETED) self.assertIsNotNone(response.json()['completed_at']) def test_invalid_state_transition(self): """Test that invalid state transitions are rejected""" # Create a shipment in CREATED state shipment = Shipment.objects.create( recipient_name='Ahmed Hassan', street_address='26 July Street', city='Cairo', state_province='Cairo Governorate', postal_code='11511', country='Egypt' ) # Try to mark it as shipped directly (invalid transition) response = self.client.post(f'/api/2/shipments/{shipment.id}/mark_shipped/') self.assertEqual(response.status_code, 400) self.assertIn('error', response.json()) self.assertIn('Invalid state transition', response.json()['error']) # Verify state hasn't changed shipment.refresh_from_db() self.assertEqual(shipment.state, Shipment.CREATED) def test_add_items_without_review_requirement(self): """Test adding items without specifying review requirement""" # Create a shipment first shipment = Shipment.objects.create( recipient_name='Wei Chen', street_address='88 Century Avenue', city='Shanghai', state_province='Shanghai', postal_code='200120', country='China' ) data = { 'item_ids': [self.item1.id, self.item2.id] } response = self.client.post(f'/api/2/shipments/{shipment.id}/add_items/', data, content_type='application/json') self.assertEqual(response.status_code, 400) self.assertIn('error', response.json()) # Verify state hasn't changed shipment.refresh_from_db() self.assertEqual(shipment.state, Shipment.CREATED) def test_approve_without_items(self): """Test approving a shipment without items""" # Create a shipment with REVIEW_ITEMS requirement shipment = Shipment.objects.create( recipient_name='Raj Patel', street_address='123 MG Road', city='Mumbai', state_province='Maharashtra', postal_code='400001', country='India', state=Shipment.PENDING_REVIEW, review_requirement=Shipment.REVIEW_ITEMS ) response = self.client.post(f'/api/2/shipments/{shipment.id}/approve/') self.assertEqual(response.status_code, 400) self.assertIn('error', response.json()) # Verify state hasn't changed shipment.refresh_from_db() self.assertEqual(shipment.state, Shipment.PENDING_REVIEW) def test_approve_without_address(self): """Test approving a shipment without complete address""" # Create a shipment with REVIEW_ADDRESS requirement shipment = Shipment.objects.create( recipient_name='Sofia Oliveira', street_address='Avenida Paulista 1000', city='São Paulo', state_province='São Paulo', postal_code='01310-100', country='', # Missing country state=Shipment.PENDING_REVIEW, review_requirement=Shipment.REVIEW_ADDRESS ) shipment.related_items.add(self.item1) response = self.client.post(f'/api/2/shipments/{shipment.id}/approve/') self.assertEqual(response.status_code, 400) self.assertIn('error', response.json()) # Verify state hasn't changed shipment.refresh_from_db() self.assertEqual(shipment.state, Shipment.PENDING_REVIEW)