def test_invalid_certificate_id(self, api_client, shipment_alice_with_device, tracking_data): signed_tracking_data = self.sign_tracking( tracking_data, self.device, certificate_id="NotARealCertificateId") response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_403( response, error= 'Certificate: NotARealCertificateId, is invalid: Parameter validation ' 'failed:\nInvalid length for parameter certificateId, value: 21, valid range: 64-inf' ) signed_tracking_data = self.sign_tracking(tracking_data, self.device, certificate_id=('a' * 64)) response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_403( response, error=f'Certificate: {"a" * 64}, is invalid: An error occurred ' f'(ResourceNotFoundException) when calling the DescribeCertificate ' f'operation: The specified resource does not exist') self.mocked_iot.update_certificate( certificateId=self.device.certificate_id, newStatus='REVOKED') response = api_client.post( self.url_device, {'payload': self.sign_tracking(tracking_data, self.device)}) AssertionHelper.HTTP_403( response, error= f'Certificate {self.device.certificate_id} is not ACTIVE in IoT for shipment' ) with open('tests/data/cert2.pem', 'r') as cert_file: bad_cert = cert_file.read() cert_response = self.mocked_iot.register_certificate( certificatePem=bad_cert, status='ACTIVE') signed_tracking_data = self.sign_tracking( tracking_data, self.device, certificate_id=cert_response['certificateId']) response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_403( response, error= f'Unexpected error: No certificates found for device {self.device.id} in AWS IoT,' f' occurred while trying to retrieve device: {self.device.id}, from AWS IoT' )
def assert_write_access(self, client, shipment_access=None, tags_access=None, documents_access=None, notes_access=None, all_access=None): if all_access is not None: shipment_access = all_access tags_access = all_access documents_access = all_access notes_access = all_access shipment_data = {'carriers_scac': 'h4x3d'} tag_data = { 'tag_type': 'foo', 'tag_value': 'bar', 'owner_id': client.handler._force_user.id, } document_data = { 'name': 'Test BOL', 'file_type': 'PDF', 'document_type': 'AIR_WAYBILL' } note_data = {'message': 'hello, world.'} if shipment_access is not None: response = client.patch(self.endpoint_urls['shipment'], shipment_data) AssertionHelper.HTTP_202( response) if shipment_access else AssertionHelper.HTTP_403( response) if tags_access is not None: response = client.post(self.endpoint_urls['tags'], tag_data) AssertionHelper.HTTP_201( response) if tags_access else AssertionHelper.HTTP_403( response) if documents_access is not None: response = client.post(self.endpoint_urls['documents'], document_data) AssertionHelper.HTTP_201( response) if documents_access else AssertionHelper.HTTP_403( response) if notes_access is not None: response = client.post(self.endpoint_urls['notes'], note_data) AssertionHelper.HTTP_201( response) if notes_access else AssertionHelper.HTTP_403( response)
def test_with_permission_links(self, shipment_alice, client_bob, new_rw_access_request_bob, permission_link_shipment_alice): shipment_data = {'carriers_scac': 'h4x3d'} tag_data = { 'tag_type': 'foo', 'tag_value': 'bar', 'owner_id': client_bob.handler._force_user.id, } # Documents/notes are not accessible via permission link, tracking/telemetry is RO response = client_bob.get( f'{self.endpoint_urls["shipment"]}?permission_link={permission_link_shipment_alice.id}' ) AssertionHelper.HTTP_200( response, entity_refs=AssertionHelper.EntityRef( resource='Shipment', relationships={ 'tags': AssertionHelper.EntityRef(resource='ShipmentTag', pk=self.tag.id) }), included=AssertionHelper.EntityRef(resource='ShipmentTag'), ) response = client_bob.patch( f'{self.endpoint_urls["shipment"]}?permission_link={permission_link_shipment_alice.id}', shipment_data) AssertionHelper.HTTP_403(response) response = client_bob.post( f'{self.endpoint_urls["tags"]}?permission_link={permission_link_shipment_alice.id}', tag_data) AssertionHelper.HTTP_403(response) new_rw_access_request_bob.approved = True new_rw_access_request_bob.save() response = client_bob.patch( f'{self.endpoint_urls["shipment"]}?permission_link={permission_link_shipment_alice.id}', shipment_data) AssertionHelper.HTTP_202(response) response = client_bob.post( f'{self.endpoint_urls["tags"]}?permission_link={permission_link_shipment_alice.id}', tag_data) AssertionHelper.HTTP_201(response)
def test_requires_authentication(self, api_client): response = api_client.post(self.shipment_alice_url, { 'name': 'Test BOL', 'document_type': 'Bol', 'file_type': 'Pdf' }) AssertionHelper.HTTP_403(response)
def test_pickup_with_gtx_required(client_alice, shipment): assert shipment.pickup_act is None shipment.gtx_required = True shipment.save() url = reverse('shipment-actions', kwargs={ 'version': 'v1', 'shipment_pk': shipment.id }) action = {'action_type': ActionType.PICK_UP.name} # If gtx_required, pickup requires an asset_physical_id response = client_alice.post(url, data=action) AssertionHelper.HTTP_403( response, error= 'In order to proceed with this shipment pick up, you need to provide a ' 'value for the field [Shipment.asset_physical_id]') action['asset_physical_id'] = 'nfc_tag' response = client_alice.post(url, data=action) AssertionHelper.HTTP_200( response, entity_refs=AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id, attributes={'state': TransitState.IN_TRANSIT.name}))
def test_nonwallet_owner_fails(self, client_bob, mock_non_wallet_owner_calls): response = client_bob.get(self.history_url) AssertionHelper.HTTP_403( response, error='You do not have permission to perform this action.', vnd=False) mock_non_wallet_owner_calls.assert_calls(self.assert_fail)
def test_non_org_user_shipment_note_creation(client_bob, shipment, mocked_not_shipper, mocked_not_carrier, mocked_not_moderator, nonsuccessful_wallet_owner_calls_assertions): url = reverse('shipment-notes-list', kwargs={'version': 'v1', 'shipment_pk': shipment.id}) # user2 is an authenticated user from another Org. he cannot create a shipment note response = client_bob.post(url, {'message': MESSAGE_1}) AssertionHelper.HTTP_403(response, error='You do not have permission to perform this action.') mocked_not_moderator.assert_calls(nonsuccessful_wallet_owner_calls_assertions)
def test_dropoff_asset_physical_id(client_alice, shipment): assert shipment.delivery_act is None # Set NFC/Asset ID super_secret_nfc_id = 'package123' shipment.pick_up(asset_physical_id=super_secret_nfc_id) shipment.save() shipment.arrival() shipment.save() url = reverse('shipment-actions', kwargs={ 'version': 'v1', 'shipment_pk': shipment.id }) action = {'action_type': ActionType.DROP_OFF.name} # asset_physical_id set on instance but no raw_asset_physical_id should return error response = client_alice.post(url, data=action) AssertionHelper.HTTP_403( response, error='Hash of asset tag does not match value specified in ' 'Shipment.asset_physical_id') action['raw_asset_physical_id'] = 'package987' # asset_physical_id set on instance but incorrect raw_asset_physical_id should return error response = client_alice.post(url, data=action) AssertionHelper.HTTP_403( response, error='Hash of asset tag does not match value specified in ' 'Shipment.asset_physical_id') action['raw_asset_physical_id'] = super_secret_nfc_id # asset_physical_id set on instance and matching raw_asset_physical_id should succeed response = client_alice.post(url, data=action) AssertionHelper.HTTP_200( response, entity_refs=AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id, attributes={'state': TransitState.DELIVERED.name}))
def test_expired_permission_link_fails( self, api_client, permission_link_device_shipment_expired): response = api_client.get( f'{self.url}?permission_link={permission_link_device_shipment_expired.id}' ) AssertionHelper.HTTP_403( response, vnd=False, error='You do not have permission to perform this action.')
def test_authenticated_user_not_in_shipment_org(client_bob, mocked_not_shipper, mocked_not_carrier, mocked_not_moderator, shipment, shipment_tag_creation_data): url = reverse('shipment-tags-list', kwargs={'version': 'v1', 'shipment_pk': shipment.id}) # User2 does not belong to the shipment organization and is neither shipper, carrier nor moderator. # he should not be able to tag the shipment response = client_bob.post(url, shipment_tag_creation_data) AssertionHelper.HTTP_403(response, error='You do not have permission to perform this action.')
def test_shipper_carrier_moderator_shipment_tag(org_id_bob, client_bob, mocked_is_shipper, mocked_not_carrier, mocked_not_moderator, shipment, shipment_tag_creation_data): url = reverse('shipment-tags-list', kwargs={'version': 'v1', 'shipment_pk': shipment.id}) # User2 does not belong to the shipment organization but is the shipment shipper. # he should be not able to tag the shipment response = client_bob.post(url, shipment_tag_creation_data) AssertionHelper.HTTP_403(response, error='You do not have permission to perform this action.')
def test_requires_shipment_access(self, client_bob, mock_non_wallet_owner_calls, nonsuccessful_wallet_owner_calls_assertions): response = client_bob.post(self.shipment_alice_url, { 'name': 'Test BOL', 'document_type': 'Bol', 'file_type': 'Pdf' }) AssertionHelper.HTTP_403(response) mock_non_wallet_owner_calls.assert_calls(nonsuccessful_wallet_owner_calls_assertions)
def test_non_wallet_owner_fails( self, client_bob, nonsuccessful_wallet_owner_calls_assertions, mock_non_wallet_owner_calls): response = client_bob.get(self.url) AssertionHelper.HTTP_403( response, vnd=False, error='You do not have permission to perform this action.') mock_non_wallet_owner_calls.assert_calls( nonsuccessful_wallet_owner_calls_assertions)
def test_invalid_key(self, api_client, tracking_data, shipment_alice_with_device): signed_tracking_data = self.sign_tracking(tracking_data, self.device, key=self.key_pem2) response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_403( response, error= 'Error validating tracking data JWS: Signature verification failed.' )
def test_create_shipment_note(user_alice, shipper_user, api_client, shipper_api_client, client_alice, shipment, mocked_is_shipper, successful_wallet_owner_calls_assertions): url = reverse('shipment-notes-list', kwargs={'version': 'v1', 'shipment_pk': shipment.id}) create_note_data, content_type = create_form_content({'message': MESSAGE_1}) # An unauthenticated user cannot create a shipment note response = api_client.post(url, {'message': MESSAGE_1}) AssertionHelper.HTTP_403(response, error='You do not have permission to perform this action.') # An authenticated request with a empty message should fail response = client_alice.post(url, {'message': ''}) AssertionHelper.HTTP_400(response, error='This field may not be blank.', pointer='message') # An authenticated request with a message with more than 500 characters should fail response = client_alice.post(url, {'message': MESSAGE_2}) AssertionHelper.HTTP_400(response, error='Ensure this value has at most 500 characters (it has 632).', pointer='message') # An authenticated user can create a shipment note response = client_alice.post(url, create_note_data, content_type=content_type) AssertionHelper.HTTP_201(response, entity_refs=AssertionHelper.EntityRef(resource='ShipmentNote', attributes={ 'message': MESSAGE_1, 'user_id': user_alice.id, 'username': get_username(user_alice), 'organization_name': get_user_org_name( user_alice)}, relationships={'shipment': AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id)}) ) # A shipper also valid for moderator and carrier can add a shipment note response = shipper_api_client.post(url, {'message': MESSAGE_1}) AssertionHelper.HTTP_201(response, entity_refs=AssertionHelper.EntityRef(resource='ShipmentNote', attributes={ 'message': MESSAGE_1, 'user_id': shipper_user.id, 'username': get_username(shipper_user)}, relationships={ 'shipment': AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id)}) ) mocked_is_shipper.assert_calls(successful_wallet_owner_calls_assertions)
def test_requires_shipment_association(self, api_client, shipment_alice, tracking_data): signed_tracking_data = self.sign_tracking(tracking_data, self.device) response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_403( response, error='No shipment/route found associated to device.') shipment_alice.device = self.device shipment_alice.save() response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_204(response) assert TrackingData.objects.all().count() == 1
def test_list_search_filter(client_alice, shipper_api_client, api_client, shipment, mocked_is_shipper, shipment_notes, successful_wallet_owner_calls_assertions, shipper_user): url = reverse('shipment-notes-list', kwargs={'version': 'v1', 'shipment_pk': shipment.id}) # An unauthenticated user cannot list a shipment notes response = api_client.get(url) AssertionHelper.HTTP_403(response) # A shipment owner can list all notes associated response = client_alice.get(url) AssertionHelper.HTTP_200(response, is_list=True, count=len(shipment_notes) - 1) # A shipper can list only the notes associated to the relative shipment response = shipper_api_client.get(url) AssertionHelper.HTTP_200(response, is_list=True, count=len(shipment_notes) - 1) mocked_is_shipper.assert_calls(successful_wallet_owner_calls_assertions) # There is only one note authored by the shipper response = client_alice.get(f'{url}?user_id={shipper_user.id}') AssertionHelper.HTTP_200(response, is_list=True, entity_refs=[AssertionHelper.EntityRef(resource='ShipmentNote', attributes={ 'message': shipment_notes[2].message, 'user_id': shipment_notes[2].user_id, 'username': shipment_notes[2].username}, relationships={ 'shipment': AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id)})], count=1 ) # There is only one note containing the word "ShipChain" response = client_alice.get(f'{url}?search=shipchain') AssertionHelper.HTTP_200(response, is_list=True, entity_refs=[AssertionHelper.EntityRef(resource='ShipmentNote', attributes={ 'message': shipment_notes[1].message, 'user_id': shipment_notes[1].user_id, 'username': shipment_notes[1].username}, relationships={ 'shipment': AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id)})], count=1 )
def test_random_device_id(self, api_client, tracking_data, shipment_alice_with_device): signed_tracking_data = self.sign_tracking(tracking_data, self.device) response = api_client.post(self.url_random, {'payload': signed_tracking_data}) AssertionHelper.HTTP_403( response, error='No shipment/route found associated to device.') # Does not matter which device id is in the data just the id in the endpoint signed_tracking_data = self.sign_tracking(tracking_data, self.device, device_id=random_id()) response = api_client.post(self.url_device, {'payload': signed_tracking_data}) AssertionHelper.HTTP_204(response)
def test_create_shipment_note(api_client, shipment): url = reverse('shipment-notes-list', kwargs={ 'version': 'v1', 'shipment_pk': shipment.id }) bad_shipment_url = reverse('shipment-notes-list', kwargs={ 'version': 'v1', 'shipment_pk': random_id() }) create_note_data, content_type = create_form_content({ 'message': MESSAGE, 'user_id': USER_ID }) # An unauthenticated user cannot create a shipment note without specifying the user response = api_client.post(url, {'message': MESSAGE}) AssertionHelper.HTTP_400(response, error='This field is required.', pointer='user_id') # A shipment note cannot be created for a non existing shipment response = api_client.post(bad_shipment_url, create_note_data, content_type=content_type) AssertionHelper.HTTP_403( response, error='You do not have permission to perform this action.') # An authenticated user with a specified user can create a shipment note response = api_client.post(url, create_note_data, content_type=content_type) AssertionHelper.HTTP_201(response, entity_refs=AssertionHelper.EntityRef( resource='ShipmentNote', attributes={ 'message': MESSAGE, 'user_id': USER_ID, 'username': None }, relationships={ 'shipment': AssertionHelper.EntityRef( resource='Shipment', pk=shipment.id) }))
def test_org_user_shipment_tag(org_id_alice, api_client, client_alice, shipment, shipment_tag_creation_data, missing_tag_type_creation_data, missing_tag_value_creation_data, space_in_tag_type_creation_data, space_in_tag_value_creation_data): url = reverse('shipment-tags-list', kwargs={'version': 'v1', 'shipment_pk': shipment.id}) # An unauthenticated user cannot tag a shipment response = api_client.post(url, shipment_tag_creation_data) AssertionHelper.HTTP_403(response, error='You do not have permission to perform this action.') # An org user cannot tag a shipment with missing tag_type in creation data response = client_alice.post(url, missing_tag_type_creation_data) AssertionHelper.HTTP_400(response, error='This field is required.') # An org user cannot tag a shipment with missing tag_value in creation data response = client_alice.post(url, missing_tag_value_creation_data) AssertionHelper.HTTP_400(response, error='This field is required.') # An org user cannot tag a shipment with space in tag_value field creation data response = client_alice.post(url, space_in_tag_value_creation_data) AssertionHelper.HTTP_400(response, error='Space(s) not allowed in this field') # An org user cannot tag a shipment with space in tag_type field creation data response = client_alice.post(url, space_in_tag_type_creation_data) AssertionHelper.HTTP_400(response, error='Space(s) not allowed in this field') # An org user with proper tag data definition, should tag a shipment response = client_alice.post(url, shipment_tag_creation_data) AssertionHelper.HTTP_201(response, entity_refs=AssertionHelper.EntityRef( resource='ShipmentTag', attributes={'tag_type': shipment_tag_creation_data['tag_type'], 'tag_value': shipment_tag_creation_data['tag_value'], 'owner_id': org_id_alice}, relationships={ 'shipment': AssertionHelper.EntityRef(resource='Shipment', pk=shipment.id) }) ) # Trying to tag a shipment with an existing (tag_type, tag_value) pair should fail response = client_alice.post(url, shipment_tag_creation_data) AssertionHelper.HTTP_400(response, error='This shipment already has a tag with the provided [tag_type] and [tag_value].')
def test_owner_shipment_device_location(client_alice, api_client, shipment_tracking_data, mocked_profiles_wallet_list, profiles_wallet_list_assertions): url = reverse('shipment-overview', kwargs={'version': 'v1'}) # An unauthenticated request should with 403 status code response = api_client.get(url) AssertionHelper.HTTP_403(response) # An authenticated user can list only the shipments # he owns with reported tracking data. In this case exactly (NUM_DEVICES - 2) response = client_alice.get(url) response_data = response.json() AssertionHelper.HTTP_200(response, vnd=True, is_list=True, count=len(shipment_tracking_data) - 1) assert len(response_data['included']) == NUM_DEVICES - 2 mocked_profiles_wallet_list.assert_calls(profiles_wallet_list_assertions)
def test_accessrequest_deletion_cache_invalidation( self, shipment_alice, client_bob, approved_access_request_bob, devices, overview_tracking_data): self.setup_devices_and_tracking(shipment_alice, devices[0], overview_tracking_data[0]) tracking_url = reverse('shipment-tracking', kwargs={ 'version': 'v1', 'pk': shipment_alice.id }) response = client_bob.get(self.shipment_overview) AssertionHelper.HTTP_200(response, is_list=True, count=1) response = client_bob.get(self.shipment_list) AssertionHelper.HTTP_200(response, is_list=True, count=1) response = client_bob.get(tracking_url) AssertionHelper.HTTP_200(response) approved_access_request_bob.delete() response = client_bob.get(self.shipment_overview) AssertionHelper.HTTP_200(response, is_list=True, count=0) response = client_bob.get(self.shipment_list) AssertionHelper.HTTP_200(response, is_list=True, count=0) response = client_bob.get(tracking_url) AssertionHelper.HTTP_403(response)
def test_requires_authentication(self, api_client): response = api_client.post(self.list_url) AssertionHelper.HTTP_403(response)
def test_nonextant_device_fails(self, api_client, create_signed_telemetry_post): response = api_client.post(self.random_telemetry_url, create_signed_telemetry_post) AssertionHelper.HTTP_403(response, error='No shipment/route found associated to device.')
def test_unauthenticated_user_fails(self, api_client, mock_non_wallet_owner_calls): response = api_client.get(self.telemetry_url) AssertionHelper.HTTP_403(response, vnd=False)
def assert_read_access(self, client, shipment_access=None, tags_access=None, documents_access=None, notes_access=None, tracking_access=None, telemetry_access=None, all_access=None): if all_access is not None: shipment_access = all_access tags_access = all_access documents_access = all_access notes_access = all_access tracking_access = all_access telemetry_access = all_access if documents_access is not None: response = client.get(self.endpoint_urls['documents']) AssertionHelper.HTTP_200( response, is_list=True ) if documents_access else AssertionHelper.HTTP_403(response) if notes_access is not None: response = client.get(self.endpoint_urls['notes']) AssertionHelper.HTTP_200( response, is_list=True ) if notes_access else AssertionHelper.HTTP_403(response) if tracking_access is not None: response = client.get(self.endpoint_urls['tracking']) AssertionHelper.HTTP_200( response) if tracking_access else AssertionHelper.HTTP_403( response) if shipment_access is not None: response = client.get(self.endpoint_urls['shipment']) AssertionHelper.HTTP_200( response) if shipment_access else AssertionHelper.HTTP_403( response) if tags_access is not None: # Tags are a special case, do not have list/retrieve, only visible via shipment model response = client.get(self.endpoint_urls['shipment']) if tags_access: AssertionHelper.HTTP_200( response, entity_refs=AssertionHelper.EntityRef( resource='Shipment', relationships={ 'tags': AssertionHelper.EntityRef(resource='ShipmentTag', pk=self.tag.id) }), included=AssertionHelper.EntityRef(resource='ShipmentTag'), ) elif response.status_code == 200: # Can only see tags if the user also has shipment access assert 'tags' not in response.json()['data']['relationships'] for included in response.json()['included']: assert included['type'] != 'ShipmentTag' if telemetry_access is not None: # Telemetry endpoint is not JSON API format response = client.get(self.endpoint_urls['telemetry']) if telemetry_access: assert response.status_code == 200 assert response.json() == [] else: assert response.status_code == 403 # Should also have access to sensors endpoint response = client.get(self.endpoint_urls['sensors']) if telemetry_access: AssertionHelper.HTTP_200(response, is_list=True) else: AssertionHelper.HTTP_403(response, vnd=False)
def test_cant_request_own(self, client_alice, access_request_ro_attributes): response = client_alice.post(self.list_url, access_request_ro_attributes) AssertionHelper.HTTP_403( response, 'You do not have permission to perform this action.')
def test_random_id_fails(self, client_bob): response = client_bob.get(self.random_url) AssertionHelper.HTTP_403( response, error='You do not have permission to perform this action.', vnd=False)
def test_random_shipment_url_fails(self, client_alice): response = client_alice.get(self.random_telemetry_url) AssertionHelper.HTTP_403(response, vnd=False)
def test_requires_authentication(self, api_client): response = api_client.get(self.history_url) AssertionHelper.HTTP_403( response, error='You do not have permission to perform this action.', vnd=False)