class AssetsEndpointUnauthorizedTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.asset = self.factory.create_asset_sample(item=self.item).model self.client = Client() def test_unauthorized_asset_post_put_patch_delete(self): collection_name = self.collection.name item_name = self.item.name asset_name = self.asset.name new_asset = self.factory.create_asset_sample(item=self.item).json updated_asset = self.factory.create_asset_sample( item=self.item, name=asset_name, sample='asset-1-updated').get_json('post') # make sure POST fails for anonymous user: path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=new_asset, content_type="application/json") self.assertStatusCode(401, response, msg="Unauthorized post was permitted.") # make sure PUT fails for anonymous user: path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=updated_asset, content_type="application/json") self.assertStatusCode(401, response, msg="Unauthorized put was permitted.") # make sure PATCH fails for anonymous user: path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=updated_asset, content_type="application/json") self.assertStatusCode(401, response, msg="Unauthorized patch was permitted.") # make sure DELETE fails for anonymous user: path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.delete(path) self.assertStatusCode(401, response, msg="Unauthorized del was permitted.")
class AssetsDeleteEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.asset = self.factory.create_asset_sample(item=self.item).model self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_endpoint_delete_asset(self): collection_name = self.collection.name item_name = self.item.name asset_name = self.asset.name path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.delete(path) self.assertStatusCode(200, response) # Check that is has really been deleted response = self.client.get(path) self.assertStatusCode(404, response) # Check that it is really not to be found in DB self.assertFalse(Asset.objects.filter(name=self.asset.name).exists(), msg="Deleted asset still found in DB") def test_asset_endpoint_delete_asset_invalid_name(self): collection_name = self.collection.name item_name = self.item.name path = (f"/{STAC_BASE_V}/collections/{collection_name}" f"/items/{item_name}/assets/non-existent-asset") response = self.client.delete(path) self.assertStatusCode(404, response)
class AssetRaceConditionTest(StacBaseTransactionTestCase): def setUp(self): self.username = '******' self.password = '******' get_user_model().objects.create_superuser(self.username, password=self.password) self.factory = Factory() self.collection_sample = self.factory.create_collection_sample( sample='collection-2', db_create=True) self.item_sample = self.factory.create_item_sample( self.collection_sample.model, sample='item-2', db_create=True) def test_asset_upsert_race_condition(self): workers = 5 status_201 = 0 asset_sample = self.factory.create_asset_sample( self.item_sample.model, sample='asset-no-checksum', ) def asset_atomic_upsert_test(worker): # This method run on separate thread therefore it requires to create a new client and # to login it for each call. client = Client() client.login(username=self.username, password=self.password) return client.put(reverse('asset-detail', args=[ self.collection_sample['name'], self.item_sample['name'], asset_sample['name'] ]), data=asset_sample.get_json('put'), content_type='application/json') # We call the PUT asset several times in parallel with the same data to make sure # that we don't have any race condition. responses, errors = self.run_parallel(workers, asset_atomic_upsert_test) for worker, response in responses: if response.status_code == 201: status_201 += 1 self.assertIn( response.status_code, [200, 201], msg= f'Unexpected response status code {response.status_code} for worker {worker}' ) self.check_stac_asset(asset_sample.json, response.json(), self.collection_sample['name'], self.item_sample['name'], ignore=['item']) self.assertEqual(status_201, 1, msg="Not only one upsert did a create !")
class AssetsUpdateEndpointAssetFileTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample(db_create=True) self.item = self.factory.create_item_sample( collection=self.collection.model, db_create=True) self.asset = self.factory.create_asset_sample(item=self.item.model, db_create=True) self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_endpoint_patch_put_href(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] asset_sample = self.asset.copy() put_payload = asset_sample.get_json('put') put_payload['href'] = 'https://testserver/non-existing-asset' patch_payload = {'href': 'https://testserver/non-existing-asset'} path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=patch_payload, content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('href', description, msg=f'Unexpected field error {description}') self.assertEqual("Found read-only property in payload", description['href'][0], msg="Unexpected error message") response = self.client.put(path, data=put_payload, content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('href', description, msg=f'Unexpected field error {description}') self.assertEqual("Found read-only property in payload", description['href'][0], msg="Unexpected error message")
class AssetsUnimplementedEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_unimplemented_post(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, required_only=True) response = self.client.post( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets', data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(405, response)
class AssetsUpdateEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample(db_create=True) self.item = self.factory.create_item_sample( collection=self.collection.model, db_create=True) self.asset = self.factory.create_asset_sample(item=self.item.model, db_create=True) self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_put_dont_exists(self): collection_name = self.collection['name'] item_name = self.item['name'] payload_json = self.factory.create_asset_sample( item=self.item.model, sample='asset-2', create_asset_file=False).get_json('put') # the dataset to update does not exist yet path = \ (f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/" f"{payload_json['id']}") response = self.client.put(path, data=payload_json, content_type='application/json') self.assertStatusCode(404, response) def test_asset_endpoint_put(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', checksum_multihash=self.asset['checksum_multihash'], create_asset_file=False) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json('put'), content_type="application/json") json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name, ignore=['item']) # Check the data by reading it back response = self.client.get(path) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name, ignore=['item']) def test_asset_endpoint_put_extra_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', checksum_multihash=self.asset['checksum_multihash'], extra_attribute='not allowed', create_asset_file=False) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json('put'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( {'extra_attribute': ['Unexpected property in payload']}, response.json()['description'], msg='Unexpected error message') def test_asset_endpoint_put_read_only_in_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, checksum_multihash=self.asset['checksum_multihash'], sample='asset-1-updated', created=utc_aware(datetime.utcnow()), create_asset_file=False) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json( 'put', keep_read_only=True), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'created': ['Found read-only property in payload']}, response.json()['description'], msg='Unexpected error message') def test_asset_endpoint_put_rename_asset(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] new_asset_name = "new-asset-name" changed_asset = self.factory.create_asset_sample( item=self.item.model, name=new_asset_name, sample='asset-1-updated', checksum_multihash=self.asset['checksum_multihash']) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json('put'), content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() self.assertEqual(changed_asset.json['id'], json_data['id']) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name, ignore=['item']) # Check the data by reading it back response = self.client.get( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}' f'/assets/{new_asset_name}') json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name, ignore=['item']) def test_asset_endpoint_patch_rename_asset(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] new_asset_name = "new-asset-name" changed_asset = self.factory.create_asset_sample( item=self.item.model, name=new_asset_name, sample='asset-1-updated') path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=changed_asset.get_json('patch'), content_type="application/json") json_data = response.json() self.assertStatusCode(200, response) self.assertEqual(changed_asset.json['id'], json_data['id']) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name, ignore=['item']) # Check the data by reading it back response = self.client.get( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}' f'/assets/{new_asset_name}') json_data = response.json() self.assertStatusCode(200, response) self.assertEqual(changed_asset.json['id'], json_data['id']) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name, ignore=['item']) def test_asset_endpoint_patch_extra_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', extra_payload='invalid') path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=changed_asset.get_json('patch'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'extra_payload': ['Unexpected property in payload']}, response.json()['description'], msg='Unexpected error message') def test_asset_endpoint_patch_read_only_in_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', created=utc_aware(datetime.utcnow())) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=changed_asset.get_json( 'patch', keep_read_only=True), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'created': ['Found read-only property in payload']}, response.json()['description'], msg='Unexpected error message')
class AssetsUpdateEndpointAssetFileTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample(db_create=True) self.item = self.factory.create_item_sample( collection=self.collection.model, db_create=True) self.asset = self.factory.create_asset_sample(item=self.item.model, db_create=True) self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_endpoint_patch_checksum(self): new_file_content = b'New file content' new_multihash = get_sha256_multihash(new_file_content) collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] # upload first a new file on S3 upload_file_on_s3(f'{collection_name}/{item_name}/{asset_name}', new_file_content) patch_payload = {'checksum:multihash': new_multihash} patch_asset = self.asset.copy() patch_asset['checksum_multihash'] = new_multihash path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=patch_payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() self.check_stac_asset(patch_asset.json, json_data, collection_name, item_name, ignore=['item']) # Check the data by reading it back response = self.client.get(path) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(patch_asset.json, json_data, collection_name, item_name, ignore=['item']) def test_asset_endpoint_patch_put_href(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] asset_sample = self.asset.copy() put_payload = asset_sample.get_json('put') put_payload['href'] = 'https://testserver/non-existing-asset' patch_payload = {'href': 'https://testserver/non-existing-asset'} path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=patch_payload, content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('href', description, msg=f'Unexpected field error {description}') self.assertEqual("Found read-only property in payload", description['href'][0], msg="Unexpected error message") response = self.client.put(path, data=put_payload, content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('href', description, msg=f'Unexpected field error {description}') self.assertEqual("Found read-only property in payload", description['href'][0], msg="Unexpected error message")
class AssetsWriteEndpointAssetFileTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_endpoint_post_asset_file_dont_exists(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, create_asset_file=False) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('href', description, msg=f'Unexpected field error {description}') self.assertEqual( "Asset doesn't exists at href http://testserver/collection-1/item-1/asset-1", description['href'][0], msg="Unexpected error message") # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") # NOTE: Unfortunately this test cannot be done with the moto mocking. # def test_asset_endpoint_post_s3_not_answering(self): # collection_name = self.collection.name # item_name = self.item.name # asset = self.factory.create_asset_sample(item=self.item) # path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' # response = self.client.post( # path, data=asset.get_json('post'), content_type="application/json" # ) # self.assertStatusCode(400, response) # description = response.json()['description'] # self.assertIn('href', description, msg=f'Unexpected field error {description}') # self.assertEqual( # "href location not responding", description['href'][0], msg="Unexpected error message" # ) # # Make sure that the asset is not found in DB # self.assertFalse( # Asset.objects.filter(name=asset.json['id']).exists(), # msg="Invalid asset has been created in DB" # ) def test_asset_endpoint_post_s3_without_sha256(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, create_asset_file=False) upload_file_on_s3(f'{collection_name}/{item_name}/{asset["name"]}', asset["file"], params={}) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('non_field_errors', description, msg=f'Unexpected field error {description}') self.assertEqual( "Asset at href http://testserver/collection-1/item-1/asset-1 has a md5 multihash while " "a sha2-256 multihash is defined in the checksum:multihash attribute", description['non_field_errors'][0], msg="Unexpected error message") # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") def test_asset_endpoint_post_wrong_checksum(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, create_asset_file=True) asset_json = asset.get_json('post') asset_json['checksum:multihash'] = get_sha256_multihash( b'new dummy content that do not match real checksum') path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset_json, content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('non_field_errors', description, msg=f'Unexpected field error {description}') self.assertEqual( "Asset at href http://testserver/collection-1/item-1/asset-1 with sha2-256 hash " "a7f5e7ca03b0f80a2fcfe5142642377e7654df2dfa736fe4d925322d8a651efe doesn't match the " "checksum:multihash 3db85f41709d08bf1f2907042112bf483b28e12db4b3ffb5428a1f28308847ba", description['non_field_errors'][0], msg="Unexpected error message") # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB")
class AssetsCreateEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_upsert_create_only_required(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, required_only=True) path = \ f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset["name"]}' response = self.client.put(path, data=asset.get_json('put'), content_type="application/json") json_data = response.json() self.assertStatusCode(201, response) self.check_header_location(f"{path}", response) self.check_stac_asset(asset.json, json_data, collection_name, item_name) # Check the data by reading it back response = self.client.get(response['Location']) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(asset.json, json_data, collection_name, item_name) # make sure that the optional fields are not present self.assertNotIn('geoadmin:lang', json_data) self.assertNotIn('geoadmin:variant', json_data) self.assertNotIn('proj:epsg', json_data) self.assertNotIn('eo:gsd', json_data) self.assertNotIn('description', json_data) self.assertNotIn('title', json_data) self.assertNotIn('checksum:multihash', json_data) def test_asset_upsert_create(self): collection = self.collection item = self.item asset = self.factory.create_asset_sample(item=item, sample='asset-no-checksum', create_asset_file=False) asset_name = asset['name'] response = self.client.get( reverse('asset-detail', args=[collection.name, item.name, asset_name])) # Check that assert does not exist already self.assertStatusCode(404, response) # Check also, that the asset does not exist in the DB already self.assertFalse(Asset.objects.filter(name=asset_name).exists(), msg="Asset already exists") # Now use upsert to create the new asset response = self.client.put(reverse( 'asset-detail', args=[collection.name, item.name, asset_name]), data=asset.get_json('put'), content_type="application/json") json_data = response.json() self.assertStatusCode(201, response) self.check_header_location( reverse('asset-detail', args=[collection.name, item.name, asset_name]), response) self.check_stac_asset(asset.json, json_data, collection.name, item.name) # make sure that all optional fields are present self.assertIn('geoadmin:lang', json_data) self.assertIn('geoadmin:variant', json_data) self.assertIn('proj:epsg', json_data) self.assertIn('eo:gsd', json_data) self.assertIn('description', json_data) self.assertIn('title', json_data) # Checksum multihash is set by the AssetUpload later on self.assertNotIn('checksum:multihash', json_data) # Check the data by reading it back response = self.client.get(response['Location']) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(asset.json, json_data, collection.name, item.name) def test_asset_upsert_create_non_existing_parent_item_in_path(self): collection = self.collection item = self.item asset = self.factory.create_asset_sample(item=item, create_asset_file=False) asset_name = asset['name'] path = ( f'/{STAC_BASE_V}/collections/{collection.name}/items/non-existing-item/assets/' f'{asset_name}') # Check that asset does not exist already response = self.client.get(path) self.assertStatusCode(404, response) # Check also, that the asset does not exist in the DB already self.assertFalse(Asset.objects.filter(name=asset_name).exists(), msg="Deleted asset still found in DB") # Now use upsert to create the new asset response = self.client.put(path, data=asset.get_json('put'), content_type="application/json") self.assertStatusCode(404, response) def test_asset_upsert_create_non_existing_parent_collection_in_path(self): item = self.item asset = self.factory.create_asset_sample(item=item, create_asset_file=False) asset_name = asset['name'] path = ( f'/{STAC_BASE_V}/collections/non-existing-collection/items/{item.name}/assets/' f'{asset_name}') # Check that asset does not exist already response = self.client.get(path) self.assertStatusCode(404, response) # Check also, that the asset does not exist in the DB already self.assertFalse(Asset.objects.filter(name=asset_name).exists(), msg="Deleted asset still found in DB") # Now use upsert to create the new asset response = self.client.put(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(404, response) def test_asset_upsert_create_empty_string(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, required_only=True, description='', geoadmin_variant='', geoadmin_lang='', title='') path = \ f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset["name"]}' response = self.client.put(path, data=asset.get_json('put'), content_type="application/json") self.assertStatusCode(400, response) json_data = response.json() for field in [ 'description', 'title', 'geoadmin:lang', 'geoadmin:variant' ]: self.assertIn(field, json_data['description'], msg=f'Field {field} error missing') def test_asset_upsert_create_invalid_data(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, sample='asset-invalid') path = \ f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset["name"]}' response = self.client.put(path, data=asset.get_json('put'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( { 'eo:gsd': ['A valid number is required.'], 'geoadmin:lang': ['"12" is not a valid choice.'], 'proj:epsg': ['A valid integer is required.'], 'type': ['"dummy" is not a valid choice.'] }, response.json()['description'], msg='Unexpected error message', ) # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") def test_asset_upsert_create_characters_geoadmin_variant(self): # valid geoadmin:variant collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample( item=self.item, sample='asset-valid-geoadmin-variant') path = \ f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset["name"]}' response = self.client.put(path, data=asset.get_json('put'), content_type="application/json") self.assertStatusCode(201, response) # invalid geoadmin:variant asset = self.factory.create_asset_sample( item=self.item, sample='asset-invalid-geoadmin-variant') path = \ f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset["name"]}' response = self.client.put(path, data=asset.get_json('put'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( { 'geoadmin:variant': [ 'Invalid geoadmin:variant "more than twenty-five characters with s", ' 'special characters beside one space are not allowed', 'Ensure this field has no more than 25 characters.' ] }, response.json()['description'], msg='Unexpected error message', )
class AssetUploadBaseTest(StacBaseTestCase, S3TestMixin): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.client = Client() client_login(self.client) self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.asset = self.factory.create_asset_sample( item=self.item, sample='asset-no-file').model self.maxDiff = None # pylint: disable=invalid-name def get_asset_upload_queryset(self): return AssetUpload.objects.all().filter( asset__item__collection__name=self.collection.name, asset__item__name=self.item.name, asset__name=self.asset.name, ) def get_delete_asset_path(self): return reverse( 'asset-detail', args=[self.collection.name, self.item.name, self.asset.name]) def get_get_multipart_uploads_path(self): return reverse( 'asset-uploads-list', args=[self.collection.name, self.item.name, self.asset.name]) def get_create_multipart_upload_path(self): return reverse( 'asset-uploads-list', args=[self.collection.name, self.item.name, self.asset.name]) def get_abort_multipart_upload_path(self, upload_id): return reverse('asset-upload-abort', args=[ self.collection.name, self.item.name, self.asset.name, upload_id ]) def get_complete_multipart_upload_path(self, upload_id): return reverse('asset-upload-complete', args=[ self.collection.name, self.item.name, self.asset.name, upload_id ]) def get_list_parts_path(self, upload_id): return reverse('asset-upload-parts-list', args=[ self.collection.name, self.item.name, self.asset.name, upload_id ]) def s3_upload_parts(self, upload_id, file_like, size, number_parts): s3 = get_s3_client() key = get_asset_path(self.item, self.asset.name) parts = [] # split the file into parts start = 0 offset = size // number_parts for part in range(1, number_parts + 1): # use the s3 client to upload the file instead of the presigned url due to the s3 # mocking response = s3.upload_part(Body=file_like[start:start + offset], Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=key, PartNumber=part, UploadId=upload_id) start += offset parts.append({'etag': response['ETag'], 'part_number': part}) return parts def get_file_like_object(self, size): file_like = os.urandom(size) checksum_multihash = get_sha256_multihash(file_like) return file_like, checksum_multihash def check_urls_response(self, urls, number_parts): now = utc_aware(datetime.utcnow()) self.assertEqual(len(urls), number_parts) for i, url in enumerate(urls): self.assertListEqual(list(url.keys()), ['url', 'part', 'expires'], msg='Url dictionary keys missing') self.assertEqual( url['part'], i + 1, msg=f'Part {url["part"]} does not match the url index {i}') try: url_parsed = parse.urlparse(url["url"]) self.assertIn(url_parsed[0], ['http', 'https']) except ValueError as error: self.fail( msg= f"Invalid url {url['url']} for part {url['part']}: {error}" ) try: expires_dt = fromisoformat(url['expires']) self.assertGreater( expires_dt, now, msg= f"expires {url['expires']} for part {url['part']} is not in future" ) except ValueError as error: self.fail( msg= f"Invalid expires {url['expires']} for part {url['part']}: {error}" ) def check_created_response(self, json_response): self.assertNotIn('completed', json_response) self.assertNotIn('aborted', json_response) self.assertIn('upload_id', json_response) self.assertIn('status', json_response) self.assertIn('number_parts', json_response) self.assertIn('checksum:multihash', json_response) self.assertIn('urls', json_response) self.assertEqual(json_response['status'], 'in-progress') def check_completed_response(self, json_response): self.assertNotIn('urls', json_response) self.assertNotIn('aborted', json_response) self.assertIn('upload_id', json_response) self.assertIn('status', json_response) self.assertIn('number_parts', json_response) self.assertIn('checksum:multihash', json_response) self.assertIn('completed', json_response) self.assertEqual(json_response['status'], 'completed') self.assertGreater(fromisoformat(json_response['completed']), fromisoformat(json_response['created'])) def check_aborted_response(self, json_response): self.assertNotIn('urls', json_response) self.assertNotIn('completed', json_response) self.assertIn('upload_id', json_response) self.assertIn('status', json_response) self.assertIn('number_parts', json_response) self.assertIn('checksum:multihash', json_response) self.assertIn('aborted', json_response) self.assertEqual(json_response['status'], 'aborted') self.assertGreater(fromisoformat(json_response['aborted']), fromisoformat(json_response['created']))
class AssetsModelTestCase(StacBaseTransactionTestCase): @mock_s3_asset_file def setUp(self): self.factory = Factory() self.collection = self.factory.create_collection_sample( db_create=True).model self.item = self.factory.create_item_sample(collection=self.collection, db_create=True).model self.asset = self.factory.create_asset_sample(item=self.item, db_create=True) def test_create_already_existing_asset(self): # try to create already existing asset twice with self.assertRaises(ValidationError, msg="Existing asset could be re-created."): asset = Asset(**self.asset.attributes) asset.full_clean() asset.save() def test_create_asset_invalid_name(self): # try to create a asset with invalid asset name and other invalid fields with self.assertRaises(ValidationError, msg="asset with invalid name was accepted."): self.factory.create_asset_sample(item=self.item, sample="asset-invalid", db_create=True) # db_create=True implicitly creates the asset file. # That's why it is used here and in the following tests def test_create_asset_missing_mandatory_fields(self): # try to create an asset with missing mandatory attributes. with self.assertRaises( ValidationError, msg="asset with missing mandatory fields was accepted."): self.factory.create_asset_sample( item=self.item, sample="asset-missing-required", db_create=True, ) def test_create_asset_valid_geoadmin_variant(self): # try to create an asset with valid geoadmin variant. This should not raise any error. self.factory.create_asset_sample( item=self.item, sample="asset-valid-geoadmin-variant", db_create=True, ) def test_create_asset_invalid_eo_gsd(self): with self.assertRaises(ValidationError, msg="asset with invalid eo:gsd was accepted."): self.factory.create_asset_sample( item=self.item, eo_gsd=0.0, db_create=True, ) def test_create_asset_valid_eo_gsd(self): asset = self.factory.create_asset_sample(item=self.item, eo_gsd=1.33, db_create=True).model self.collection.refresh_from_db() self.assertListEqual(self.collection.summaries_proj_epsg, [2056]) self.assertListEqual(self.collection.summaries_eo_gsd, [1.33, 3.4]) self.assertListEqual(self.collection.summaries_geoadmin_variant, ['kgrs']) asset.delete() self.collection.refresh_from_db() self.assertListEqual(self.collection.summaries_proj_epsg, [2056]) self.assertListEqual(self.collection.summaries_eo_gsd, [3.4]) self.assertListEqual(self.collection.summaries_geoadmin_variant, ['kgrs']) def test_create_asset_invalid_geoadmin_variant(self): # try to create an asset with invalid geoadmin variant. with self.assertRaises( ValidationError, msg="asset with invalid geoadmin variant was accepted."): self.factory.create_asset_sample( item=self.item, sample="asset-invalid-geoadmin-variant", db_create=True, ) def test_create_asset_only_required_attributes(self): # try to create an asset with with only the required attributes. # Should not raise any errors. self.factory.create_asset_sample(item=self.item, sample="asset-valid-geoadmin-variant", db_create=True, required_only=True) def test_create_update_asset_invalid_media_type(self): # try to create an asset with invalid media type with self.assertRaises( ValidationError, msg="asset with invalid media type was accepted.") as context: self.factory.create_asset_sample( item=self.item, name='my-asset.yaml', media_type="application/vnd.oai.openapi+yaml;version=3.0", db_create=True, ) exception = context.exception self.assertIn( "Invalid id extension '.yaml', id must have a valid file extension", exception.messages) self.assertIn( "Value 'application/vnd.oai.openapi+yaml;version=3.0' is not a valid choice.", exception.messages) self.assertIn( 'Invalid media type application/vnd.oai.openapi+yaml;version=3.0', exception.messages) with self.assertRaises( ValidationError, msg="asset with name missmatch media type was accepted." ) as context: self.factory.create_asset_sample( item=self.item, name='my-asset.txt', media_type="application/json", db_create=True, ) exception = context.exception self.assertIn( "Invalid id extension '.txt', id must match its media type application/json", exception.messages) # Test invalid media type/name update asset = self.factory.create_asset_sample( item=self.item, name='asset.xml', media_type='application/gml+xml').model with self.assertRaises( ValidationError, msg="asset with name missmatch media type was accepted." ) as context: asset.name = 'asset.zip' asset.full_clean() asset.save() asset.refresh_from_db() exception = context.exception self.assertIn( "Invalid id extension '.zip', id must match its media type application/gml+xml", exception.messages) with self.assertRaises( ValidationError, msg="asset with name missmatch media type was accepted." ) as context: asset.media_type = 'text/plain' asset.full_clean() asset.save() asset.refresh_from_db() exception = context.exception self.assertIn( "Invalid id extension '.xml', id must match its media type text/plain", exception.messages) with self.assertRaises( ValidationError, msg="asset with name missmatch media type was accepted." ) as context: asset.media_type = 'invalid/media-type' asset.full_clean() asset.save() asset.refresh_from_db() exception = context.exception self.assertIn("Value 'invalid/media-type' is not a valid choice.", exception.messages) def test_create_asset_media_type_validation(self): # try to create an asset of media type with several extensions self.factory.create_asset_sample(item=self.item, name='asset.xml', media_type='application/gml+xml', db_create=True) self.factory.create_asset_sample(item=self.item, name='asset.gml', media_type='application/gml+xml', db_create=True) def test_create_update_asset_media_type_validation(self): # try to create an asset of media type with several extensions asset = self.factory.create_asset_sample( item=self.item, name='asset.xml', media_type='application/gml+xml').model # correct the extension asset.name = 'asset.gml' asset.full_clean() asset.save() self.assertEqual(Asset.objects.get(pk=asset.pk).name, asset.name) # Change media type with same extension asset = self.factory.create_asset_sample( item=self.item, name='asset.xml', media_type='application/gml+xml').model asset.media_type = 'application/x.interlis; version=2.3' asset.full_clean() asset.save() self.assertEqual( Asset.objects.get(pk=asset.pk).media_type, asset.media_type) # Change media type and extension asset = self.factory.create_asset_sample( item=self.item, name='asset.json', media_type='application/json').model asset.name = 'asset.zip' asset.media_type = 'text/x.plain+zip' asset.full_clean() asset.save() _asset = Asset.objects.get(pk=asset.pk) self.assertEqual(_asset.name, asset.name) self.assertEqual(_asset.media_type, asset.media_type)
class AssetUploadDeleteProtectModelTestCase(TransactionTestCase, AssetUploadTestCaseMixin): @mock_s3_asset_file def setUp(self): self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection, ).model self.asset = self.factory.create_asset_sample(item=self.item).model def test_delete_asset_upload(self): upload_id = 'upload-in-progress' asset_upload = self.create_asset_upload(self.asset, upload_id) with self.assertRaises( ProtectedError, msg="Deleting an upload in progress not allowed"): asset_upload.delete() asset_upload = self.update_asset_upload( asset_upload, status=AssetUpload.Status.COMPLETED, ended=utc_aware(datetime.utcnow())) asset_upload.delete() self.assertFalse(AssetUpload.objects.all().filter( upload_id=upload_id, asset__name=self.asset.name).exists()) def test_delete_asset_with_upload_in_progress(self): asset_upload_1 = self.create_asset_upload(self.asset, 'upload-in-progress') asset_upload_2 = self.create_asset_upload( self.asset, 'upload-completed', status=AssetUpload.Status.COMPLETED, ended=utc_aware(datetime.utcnow())) asset_upload_3 = self.create_asset_upload( self.asset, 'upload-aborted', status=AssetUpload.Status.ABORTED, ended=utc_aware(datetime.utcnow())) asset_upload_4 = self.create_asset_upload( self.asset, 'upload-aborted-2', status=AssetUpload.Status.ABORTED, ended=utc_aware(datetime.utcnow())) # Try to delete parent asset with self.assertRaises(ValidationError): self.asset.delete() self.assertEqual(4, len(list(AssetUpload.objects.all()))) self.assertTrue(Asset.objects.all().filter( name=self.asset.name, item__name=self.item.name, item__collection__name=self.collection.name).exists()) self.update_asset_upload(asset_upload_1, status=AssetUpload.Status.ABORTED, ended=utc_aware(datetime.utcnow())) self.asset.delete() self.assertEqual(0, len(list(AssetUpload.objects.all()))) self.assertFalse(Asset.objects.all().filter( name=self.asset.name, item__name=self.item.name, item__collection__name=self.collection.name).exists())
class CollectionSerializationTestCase(StacBaseTransactionTestCase): @mock_s3_asset_file def setUp(self): self.data_factory = Factory() self.collection_created = utc_aware(datetime.now()) self.collection = self.data_factory.create_collection_sample( db_create=True) self.item = self.data_factory.create_item_sample( collection=self.collection.model, db_create=True) self.asset = self.data_factory.create_asset_sample( item=self.item.model, db_create=True) self.collection.model.refresh_from_db() self.maxDiff = None # pylint: disable=invalid-name def test_collection_serialization(self): collection_name = self.collection.model.name # mock a request needed for the serialization of links context = { 'request': api_request_mocker.get( f'{STAC_BASE_V}/collections/{collection_name}') } # transate to Python native: serializer = CollectionSerializer(self.collection.model, context=context) python_native = serializer.data logger.debug('python native:\n%s', pformat(python_native)) # translate to JSON: content = JSONRenderer().render(python_native) logger.debug('json string: %s', content.decode("utf-8")) expected = self.collection.get_json('serialize') expected.update({ 'created': isoformat(self.collection_created), 'crs': ['http://www.opengis.net/def/crs/OGC/1.3/CRS84'], 'extent': { 'spatial': { 'bbox': [[5.644711, 46.775054, 7.602408, 49.014995]] }, 'temporal': { 'interval': [['2020-10-28T13:05:10Z', '2020-10-28T13:05:10Z']] } }, 'itemType': 'Feature', 'links': [ OrderedDict([ ('rel', 'self'), ('href', f'http://testserver/api/stac/v0.9/collections/{collection_name}' ), ]), OrderedDict([ ('rel', 'root'), ('href', 'http://testserver/api/stac/v0.9/'), ]), OrderedDict([ ('rel', 'parent'), ('href', 'http://testserver/api/stac/v0.9/'), ]), OrderedDict([ ('rel', 'items'), ('href', f'http://testserver/api/stac/v0.9/collections/{collection_name}/items' ), ]), OrderedDict([ ('href', 'https://www.example.com/described-by'), ('rel', 'describedBy'), ('type', 'description'), ('title', 'This is an extra collection link'), ]) ], 'providers': [ { 'name': 'provider-1', 'roles': ['licensor', 'producer'], 'description': 'This is a full description of the provider', 'url': 'https://www.provider.com' }, { 'name': 'provider-2', 'roles': ['licensor'], 'description': 'This is a full description of a second provider', 'url': 'https://www.provider.com/provider-2' }, { 'name': 'provider-3', }, ], 'stac_version': settings.STAC_VERSION, 'summaries': { 'eo:gsd': [3.4], 'geoadmin:variant': ['kgrs'], 'proj:epsg': [2056], }, 'updated': isoformat(self.collection_created) }) self.check_stac_collection(expected, python_native)
class AssetsUpdateEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample(db_create=True) self.item = self.factory.create_item_sample( collection=self.collection.model, db_create=True) self.asset = self.factory.create_asset_sample(item=self.item.model, db_create=True) self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_endpoint_put(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', media_type=self.asset['media_type'], checksum_multihash=self.asset['checksum_multihash'], create_asset_file=False) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json('put'), content_type="application/json") json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name) # Check the data by reading it back response = self.client.get(path) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(changed_asset.json, json_data, collection_name, item_name) def test_asset_endpoint_put_extra_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', media_type=self.asset['media_type'], checksum_multihash=self.asset['checksum_multihash'], extra_attribute='not allowed', create_asset_file=False) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json('put'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( {'extra_attribute': ['Unexpected property in payload']}, response.json()['description'], msg='Unexpected error message') def test_asset_endpoint_put_read_only_in_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', media_type=self.asset['media_type'], created=utc_aware(datetime.utcnow()), create_asset_file=False, checksum_multihash=self.asset['checksum_multihash'], ) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json( 'put', keep_read_only=True), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( { 'created': ['Found read-only property in payload'], 'checksum:multihash': ['Found read-only property in payload'] }, response.json()['description'], msg='Unexpected error message') def test_asset_endpoint_put_rename_asset(self): # rename should not be allowed collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] new_asset_name = "new-asset-name.txt" changed_asset = self.factory.create_asset_sample( item=self.item.model, name=new_asset_name, sample='asset-1-updated', checksum_multihash=self.asset['checksum_multihash']) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.put(path, data=changed_asset.get_json('put'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'id': 'Renaming is not allowed'}, response.json()['description'], msg='Unexpected error message') # Check the data by reading it back response = self.client.get( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}' f'/assets/{asset_name}') json_data = response.json() self.assertStatusCode(200, response) self.assertEqual(asset_name, json_data['id']) # Check the data that no new entry exist response = self.client.get( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}' f'/assets/{new_asset_name}') # 404 - not found self.assertStatusCode(404, response) def test_asset_endpoint_patch_rename_asset(self): # rename should not be allowed collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] new_asset_name = "new-asset-name.txt" changed_asset = self.factory.create_asset_sample( item=self.item.model, name=new_asset_name, sample='asset-1-updated') path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=changed_asset.get_json('patch'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'id': 'Renaming is not allowed'}, response.json()['description'], msg='Unexpected error message') # Check the data by reading it back response = self.client.get( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}' f'/assets/{asset_name}') json_data = response.json() self.assertStatusCode(200, response) self.assertEqual(asset_name, json_data['id']) # Check the data that no new entry exist response = self.client.get( f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}' f'/assets/{new_asset_name}') # 404 - not found self.assertStatusCode(404, response) def test_asset_endpoint_patch_extra_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', media_type=self.asset['media_type'], extra_payload='invalid') path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=changed_asset.get_json('patch'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'extra_payload': ['Unexpected property in payload']}, response.json()['description'], msg='Unexpected error message') def test_asset_endpoint_patch_read_only_in_payload(self): collection_name = self.collection['name'] item_name = self.item['name'] asset_name = self.asset['name'] changed_asset = self.factory.create_asset_sample( item=self.item.model, name=asset_name, sample='asset-1-updated', media_type=self.asset['media_type'], created=utc_aware(datetime.utcnow())) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}' response = self.client.patch(path, data=changed_asset.get_json( 'patch', keep_read_only=True), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual({'created': ['Found read-only property in payload']}, response.json()['description'], msg='Unexpected error message') def test_asset_atomic_upsert_create_500(self): sample = self.factory.create_asset_sample(self.item.model, create_asset_file=True) # the dataset to update does not exist yet with self.settings(DEBUG_PROPAGATE_API_EXCEPTIONS=True), disableLogger( 'stac_api.apps'): response = self.client.put(reverse('test-asset-detail-http-500', args=[ self.collection['name'], self.item['name'], sample['name'] ]), data=sample.get_json('put'), content_type='application/json') self.assertStatusCode(500, response) self.assertEqual(response.json()['description'], "AttributeError('test exception')") # Make sure that the ressource has not been created response = self.client.get( reverse('asset-detail', args=[ self.collection['name'], self.item['name'], sample['name'] ])) self.assertStatusCode(404, response) def test_asset_atomic_upsert_update_500(self): sample = self.factory.create_asset_sample(self.item.model, name=self.asset['name'], create_asset_file=True) # Make sure samples is different from actual data self.assertNotEqual(sample.attributes, self.asset.attributes) # the dataset to update does not exist yet with self.settings(DEBUG_PROPAGATE_API_EXCEPTIONS=True), disableLogger( 'stac_api.apps'): # because we explicitely test a crash here we don't want to print a CRITICAL log on the # console therefore disable it. response = self.client.put(reverse('test-asset-detail-http-500', args=[ self.collection['name'], self.item['name'], sample['name'] ]), data=sample.get_json('put'), content_type='application/json') self.assertStatusCode(500, response) self.assertEqual(response.json()['description'], "AttributeError('test exception')") # Make sure that the ressource has not been created response = self.client.get( reverse('asset-detail', args=[ self.collection['name'], self.item['name'], sample['name'] ])) self.assertStatusCode(200, response) self.check_stac_asset(self.asset.json, response.json(), self.collection['name'], self.item['name'], ignore=['item'])
class AssetsEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.client = Client() self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.asset_1 = self.factory.create_asset_sample(item=self.item, name="asset-1.tiff", db_create=True) self.maxDiff = None # pylint: disable=invalid-name def test_assets_endpoint(self): collection_name = self.collection.name item_name = self.item.name # To test the assert ordering make sure to not create them in ascent order asset_2 = self.factory.create_asset_sample(item=self.item, sample='asset-2', name="asset-2.txt", db_create=True) asset_3 = self.factory.create_asset_sample(item=self.item, name="asset-0.pdf", sample='asset-3', db_create=True) response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets" ) self.assertStatusCode(200, response) json_data = response.json() logger.debug('Response (%s):\n%s', type(json_data), pformat(json_data)) self.assertIn('assets', json_data, msg='assets is missing in response') self.assertEqual(3, len(json_data['assets']), msg='Number of assets doesn\'t match the expected') # Check that the output is sorted by name asset_ids = [asset['id'] for asset in json_data['assets']] self.assertListEqual(asset_ids, sorted(asset_ids), msg="Assets are not sorted by ID") asset_samples = sorted([self.asset_1, asset_2, asset_3], key=lambda asset: asset['name']) for i, asset in enumerate(asset_samples): self.check_stac_asset(asset.json, json_data['assets'][i], collection_name, item_name) def test_assets_endpoint_collection_does_not_exist(self): collection_name = "non-existent" item_name = self.item.name response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets" ) self.assertStatusCode(404, response) def test_assets_endpoint_item_does_not_exist(self): collection_name = self.collection.name item_name = "non-existent" response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets" ) self.assertStatusCode(404, response) def test_single_asset_endpoint(self): collection_name = self.collection.name item_name = self.item.name asset_name = self.asset_1["name"] response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}" ) json_data = response.json() self.assertStatusCode(200, response) logger.debug('Response (%s):\n%s', type(json_data), pformat(json_data)) self.check_stac_asset(self.asset_1.json, json_data, collection_name, item_name) # The ETag change between each test call due to the created, updated time that are in the # hash computation of the ETag self.check_header_etag(None, response)
class CollectionsSummariesTestCase(StacBaseTransactionTestCase): y200 = utc_aware(datetime.strptime('0200-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) y8000 = utc_aware(datetime.strptime('8000-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) @mock_s3_asset_file def setUp(self): self.data_factory = Factory() self.collection = self.data_factory.create_collection_sample( name='collection-test-summaries-auto-update', db_create=True ).model def add_range_item(self, start, end, name): item = self.data_factory.create_item_sample( collection=self.collection, name=name, sample='item-2', properties_start_datetime=start, properties_end_datetime=end, db_create=True ).model return item def add_single_datetime_item(self, datetime_val, name): item = self.data_factory.create_item_sample( collection=self.collection, name=name, properties_datetime=datetime_val, db_create=True, ).model return item def add_asset(self, item, eo_gsd, geoadmin_variant, proj_epsg, geoadmin_lang=None): asset = self.data_factory.create_asset_sample( item=item, eo_gsd=eo_gsd, geoadmin_variant=geoadmin_variant, geoadmin_lang=geoadmin_lang, proj_epsg=proj_epsg, db_create=True ).model self.collection.refresh_from_db() return asset def test_update_collection_summaries_asset_insertion(self): # Tests if the collection's summaries are updated when an asset is # added to the collection's two items item1 = self.add_range_item(self.y200, self.y8000, "item1") item2 = self.add_range_item(self.y200, self.y8000, "item2") self.add_asset(item1, 1.2, "kgrs", 1234, 'de') self.assertListEqual( self.collection.summaries_eo_gsd, [1.2], "Collection's summaries[eo:gsd] has not been correctly updated " "after asset has been inserted." ) self.assertListEqual( self.collection.summaries_geoadmin_variant, ["kgrs"], "Collection's summaries[geoadmin:variant] has not been correctly " " updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_geoadmin_lang, ["de"], "Collection's summaries[geoadmin:lang] has not been correctly " " updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_proj_epsg, [1234], "Collection's summaries[proj:epsg] has not been correctly updated " "after asset has been inserted." ) self.add_asset(item2, 2.1, "komb", 4321, 'fr') self.assertListEqual( self.collection.summaries_eo_gsd, [1.2, 2.1], "Collection's summaries[eo:gsd] has not been correctly updated " "after asset has been inserted." ) self.assertListEqual( self.collection.summaries_geoadmin_variant, ["kgrs", "komb"], "Collection's summaries[geoadmin:variant] has not been correctly " "updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_geoadmin_lang, ["de", "fr"], "Collection's summaries[geoadmin:lang] has not been correctly " "updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_proj_epsg, [1234, 4321], "Collection's summaries[proj:epsg] has not been correctly updated " "after asset has been inserted." ) def test_update_collection_summaries_asset_deletion(self): # Tests if the collection's summaries are updated when assets are # deleted from the collection item1 = self.add_range_item(self.y200, self.y8000, "item1") asset1 = self.add_asset(item1, 1.2, "kgrs", 1234, 'de') asset2 = self.add_asset(item1, 2.1, "komb", 4321, 'fr') asset2.delete() self.collection.refresh_from_db() self.assertListEqual( self.collection.summaries_eo_gsd, [asset1.eo_gsd], "Collection's summaries[eo:gsd] has not been correctly updated " "after asset has been deleted." ) self.assertListEqual( self.collection.summaries_geoadmin_variant, [asset1.geoadmin_variant], "Collection's summaries[geoadmin:variant] has not been correctly " "updated after asset has been deleted." ) self.assertListEqual( self.collection.summaries_geoadmin_lang, [asset1.geoadmin_lang], "Collection's summaries[geoadmin:lang] has not been correctly " "updated after asset has been deleted." ) self.assertListEqual( self.collection.summaries_proj_epsg, [asset1.proj_epsg], "Collection's summaries[proj:epsg] has not been correctly updated " "after asset has been deleted." ) asset1.delete() self.collection.refresh_from_db() self.assertListEqual( self.collection.summaries_eo_gsd, [], "Collection's summaries[eo:gsd] has not been correctly updated " "after asset has been deleted." ) self.assertListEqual( self.collection.summaries_geoadmin_variant, [], "Collection's summaries[geoadmin:variant] has not been correctly " "updated after asset has been deleted." ) self.assertListEqual( self.collection.summaries_geoadmin_lang, [], "Collection's summaries[geoadmin:lang] has not been correctly " "updated after asset has been deleted." ) self.assertListEqual( self.collection.summaries_proj_epsg, [], "Collection's summaries[proj:epsg] has not been correctly updated " "after asset has been deleted." ) def test_update_collection_summaries_empty_asset_delete(self): # This test has been introduced due to a bug when removing an asset without eo:gsd, # proj:epsg and geoadmin:variant from a collections with summaries self.assertListEqual(self.collection.summaries_proj_epsg, []) self.assertListEqual(self.collection.summaries_geoadmin_variant, []) self.assertListEqual(self.collection.summaries_geoadmin_lang, []) self.assertListEqual(self.collection.summaries_eo_gsd, []) item = self.data_factory.create_item_sample( collection=self.collection, db_create=True ).model asset = self.data_factory.create_asset_sample( item=item, required_only=True, geoadmin_variant=None, geoadmin_lang=None, eo_gsd=None, proj_epsg=None, db_create=True ).model self.assertListEqual(self.collection.summaries_proj_epsg, []) self.assertListEqual(self.collection.summaries_geoadmin_variant, []) self.assertListEqual(self.collection.summaries_geoadmin_lang, []) self.assertListEqual(self.collection.summaries_eo_gsd, []) asset2 = self.data_factory.create_asset_sample( item=item, required_only=True, geoadmin_variant='krel', geoadmin_lang='en', eo_gsd=2, proj_epsg=2056, db_create=True ).model self.collection.refresh_from_db() self.assertIsNone(asset.geoadmin_variant) self.assertListEqual(self.collection.summaries_proj_epsg, [2056]) self.assertListEqual(self.collection.summaries_geoadmin_variant, ['krel']) self.assertListEqual(self.collection.summaries_geoadmin_lang, ['en']) self.assertListEqual(self.collection.summaries_eo_gsd, [2]) asset.delete() self.collection.refresh_from_db() self.assertListEqual(self.collection.summaries_proj_epsg, [2056]) self.assertListEqual(self.collection.summaries_geoadmin_variant, ['krel']) self.assertListEqual(self.collection.summaries_geoadmin_lang, ['en']) self.assertListEqual(self.collection.summaries_eo_gsd, [2]) asset2.delete() self.collection.refresh_from_db() self.assertListEqual(self.collection.summaries_proj_epsg, []) self.assertListEqual(self.collection.summaries_geoadmin_variant, []) self.assertListEqual(self.collection.summaries_geoadmin_lang, []) self.assertListEqual(self.collection.summaries_eo_gsd, []) def test_update_collection_summaries_asset_update(self): # Tests if collection's summaries are updated correctly after an # asset was updated item1 = self.add_range_item(self.y200, self.y8000, "item1") asset1 = self.add_asset(item1, 1.2, "kgrs", 1234, 'de') asset2 = self.add_asset(item1, 2.1, "komb", 4321, 'fr') asset1.eo_gsd = 12.34 asset1.geoadmin_variant = "krel" asset1.geoadmin_lang = "en" asset1.proj_epsg = 9999 asset1.full_clean() asset1.save() self.collection.refresh_from_db() self.assertListEqual( self.collection.summaries_eo_gsd, [2.1, 12.34], "Collection's summaries[eo:gsd] has not been correctly " "updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_geoadmin_variant, ["komb", "krel"], "Collection's summaries[geoadmin:variant] has not been " "correctly updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_geoadmin_lang, ["en", "fr"], "Collection's summaries[geoadmin:lang] has not been " "correctly updated after asset has been inserted." ) self.assertListEqual( self.collection.summaries_proj_epsg, [4321, 9999], "Collection's summaries[proj:epsg] has not been correctly " "updated after asset has been inserted." ) def test_update_collection_summaries_none_values(self): # update a variant, that as been None as a start value item = self.data_factory.create_item_sample(collection=self.collection).model asset = self.add_asset(item, None, None, None, None) self.assertListEqual(self.collection.summaries_proj_epsg, []) self.assertListEqual(self.collection.summaries_geoadmin_variant, []) self.assertListEqual(self.collection.summaries_geoadmin_lang, []) self.assertListEqual(self.collection.summaries_eo_gsd, []) asset.geoadmin_variant = "krel" asset.eo_gsd = 2 asset.proj_epsg = 2056 asset.geoadmin_lang = 'rm' asset.full_clean() asset.save() self.collection.refresh_from_db() self.assertListEqual(self.collection.summaries_proj_epsg, [2056]) self.assertListEqual(self.collection.summaries_eo_gsd, [2.0]) self.assertListEqual(self.collection.summaries_geoadmin_variant, ['krel']) self.assertListEqual(self.collection.summaries_geoadmin_lang, ['rm'])
class ApiETagPreconditionTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): self.factory = Factory() self.collection = self.factory.create_collection_sample( name='collection-1', db_create=True, ) self.item = self.factory.create_item_sample( collection=self.collection.model, name='item-1', db_create=True, ) self.asset = self.factory.create_asset_sample( item=self.item.model, name='asset-1', db_create=True, ) def test_get_precondition(self): for endpoint in [ f'collections/{self.collection["name"]}', f'collections/{self.collection["name"]}/items/{self.item["name"]}', f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}' ]: with self.subTest(endpoint=endpoint): response1 = self.client.get(f"/{STAC_BASE_V}/{endpoint}") self.assertStatusCode(200, response1) # The ETag change between each test call due to the created, updated time that are # in the hash computation of the ETag self.check_header_etag(None, response1) response2 = self.client.get( f"/{STAC_BASE_V}/{endpoint}", HTTP_IF_NONE_MATCH=response1['ETag']) self.assertEqual(response1['ETag'], response2['ETag']) self.assertStatusCode(304, response2) response3 = self.client.get(f"/{STAC_BASE_V}/{endpoint}", HTTP_IF_MATCH=response1['ETag']) self.assertEqual(response1['ETag'], response3['ETag']) self.assertStatusCode(200, response3) response4 = self.client.get(f"/{STAC_BASE_V}/{endpoint}", HTTP_IF_MATCH='"abcd"') self.assertStatusCode(412, response4) def test_put_precondition(self): client_login(self.client) for (endpoint, sample) in [ (f'collections/{self.collection["name"]}', self.factory.create_collection_sample( name=self.collection["name"], sample='collection-2', )), (f'collections/{self.collection["name"]}/items/{self.item["name"]}', self.factory.create_item_sample( collection=self.collection.model, name=self.item["name"], sample='item-2', )), (f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}', self.factory.create_asset_sample( item=self.item.model, name=self.asset["name"], sample='asset-1-updated', checksum_multihash=self.asset.model.checksum_multihash)), ]: with self.subTest(endpoint=endpoint): # Get first the ETag response = self.client.get(f"/{STAC_BASE_V}/{endpoint}") self.assertStatusCode(200, response) # The ETag change between each test call due to the created, updated time that are # in the hash computation of the ETag self.check_header_etag(None, response) etag1 = response['ETag'] response = self.client.put(f"/{STAC_BASE_V}/{endpoint}", sample.get_json('put'), content_type="application/json", HTTP_IF_MATCH='"abc"') self.assertStatusCode(412, response) response = self.client.put(f"/{STAC_BASE_V}/{endpoint}", sample.get_json('put'), content_type="application/json", HTTP_IF_MATCH=etag1) self.assertStatusCode(200, response) def test_patch_precondition(self): client_login(self.client) for (endpoint, data) in [ ( f'collections/{self.collection["name"]}', { 'title': 'New title patched' }, ), ( f'collections/{self.collection["name"]}/items/{self.item["name"]}', { 'properties': { 'title': 'New title patched' } }, ), ( f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}', { 'title': 'New title patched' }, ), ]: with self.subTest(endpoint=endpoint): # Get first the ETag response = self.client.get(f"/{STAC_BASE_V}/{endpoint}") self.assertStatusCode(200, response) # The ETag change between each test call due to the created, updated time that are # in the hash computation of the ETag self.check_header_etag(None, response) etag1 = response['ETag'] response = self.client.patch(f"/{STAC_BASE_V}/{endpoint}", data, content_type="application/json", HTTP_IF_MATCH='"abc"') self.assertStatusCode(412, response) response = self.client.patch(f"/{STAC_BASE_V}/{endpoint}", data, content_type="application/json", HTTP_IF_MATCH=etag1) self.assertStatusCode(200, response) def test_delete_precondition(self): client_login(self.client) for endpoint in [ f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}', f'collections/{self.collection["name"]}/items/{self.item["name"]}', # f'collections/{self.collection["name"]}', ]: with self.subTest(endpoint=endpoint): # Get first the ETag response = self.client.get(f"/{STAC_BASE_V}/{endpoint}") self.assertStatusCode(200, response) # The ETag change between each test call due to the created, updated time that are # in the hash computation of the ETag self.check_header_etag(None, response) etag1 = response['ETag'] response = self.client.delete(f"/{STAC_BASE_V}/{endpoint}", content_type="application/json", HTTP_IF_MATCH='"abc"') self.assertStatusCode( 412, response, msg='Request should be refused due to precondition failed') response = self.client.delete(f"/{STAC_BASE_V}/{endpoint}", content_type="application/json", HTTP_IF_MATCH=etag1) self.assertStatusCode(200, response)
class AssetsWriteEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.client = Client() client_login(self.client) self.maxDiff = None # pylint: disable=invalid-name def test_asset_endpoint_post_only_required(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, required_only=True, create_asset_file=True, file=b'Dummy file content') path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") json_data = response.json() self.assertStatusCode(201, response) self.check_header_location(f"{path}/{asset['name']}", response) self.check_stac_asset(asset.json, json_data, collection_name, item_name, ignore=['item']) # Check the data by reading it back response = self.client.get(response['Location']) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(asset.json, json_data, collection_name, item_name, ignore=['item']) # make sure that the optional fields are not present self.assertNotIn('geoadmin:lang', json_data) self.assertNotIn('geoadmin:variant', json_data) self.assertNotIn('proj:epsg', json_data) self.assertNotIn('eo:gsd', json_data) self.assertNotIn('description', json_data) self.assertNotIn('title', json_data) def test_asset_endpoint_post_full(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") json_data = response.json() self.assertStatusCode(201, response) self.check_header_location(f"{path}/{asset['name']}", response) self.check_stac_asset(asset.json, json_data, collection_name, item_name, ignore=['item']) # Check the data by reading it back response = self.client.get(response['Location']) json_data = response.json() self.assertStatusCode(200, response) self.check_stac_asset(asset.json, json_data, collection_name, item_name, ignore=['item']) def test_asset_endpoint_post_empty_string(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, required_only=True, description='', geoadmin_variant='', geoadmin_lang='', title='', create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(400, response) json_data = response.json() for field in [ 'description', 'title', 'geoadmin:lang', 'geoadmin:variant' ]: self.assertIn(field, json_data['description'], msg=f'Field {field} error missing') def test_asset_endpoint_post_extra_payload(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, extra_attribute='not allowed', create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( {'extra_attribute': ['Unexpected property in payload']}, response.json()['description'], msg='Unexpected error message') # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") def test_asset_endpoint_post_read_only_in_payload(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, created=utc_aware( datetime.utcnow()), create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post', keep_read_only=True), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( { 'created': ['Found read-only property in payload'], 'href': ['Found read-only property in payload'] }, response.json()['description'], msg='Unexpected error message', ) # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") def test_asset_endpoint_post_read_only_href_in_payload(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample( item=self.item, href='https://testserver/test.txt', create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post', keep_read_only=True), content_type="application/json") self.assertStatusCode(400, response) description = response.json()['description'] self.assertIn('href', description, msg=f'Unexpected field error {description}') self.assertEqual("Found read-only property in payload", description['href'][0], msg="Unexpected error message") # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") def test_asset_endpoint_post_invalid_data(self): collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample(item=self.item, sample='asset-invalid', create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( { 'eo:gsd': ['A valid number is required.'], 'geoadmin:lang': ['"12" is not a valid choice.'], 'proj:epsg': ['A valid integer is required.'], 'type': ['"dummy" is not a valid choice.'] }, response.json()['description'], msg='Unexpected error message', ) # Make sure that the asset is not found in DB self.assertFalse(Asset.objects.filter(name=asset.json['id']).exists(), msg="Invalid asset has been created in DB") def test_asset_endpoint_post_characters_geoadmin_variant(self): # valid geoadmin:variant collection_name = self.collection.name item_name = self.item.name asset = self.factory.create_asset_sample( item=self.item, sample='asset-valid-geoadmin-variant', create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(201, response) # invalid geoadmin:variant asset = self.factory.create_asset_sample( item=self.item, sample='asset-invalid-geoadmin-variant', create_asset_file=True) path = f'/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets' response = self.client.post(path, data=asset.get_json('post'), content_type="application/json") self.assertStatusCode(400, response) self.assertEqual( { 'geoadmin:variant': [ 'Invalid geoadmin:variant "more than twenty-five characters with s", ' 'special characters beside one space are not allowed', 'Ensure this field has no more than 25 characters.' ] }, response.json()['description'], msg='Unexpected error message', )
class ApiETagPreconditionTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): self.factory = Factory() self.collection = self.factory.create_collection_sample( name='collection-1', db_create=True, ) self.item = self.factory.create_item_sample( collection=self.collection.model, name='item-1', db_create=True, ) self.asset = self.factory.create_asset_sample( item=self.item.model, name='asset-1.tiff', db_create=True, ) def get_etag(self, endpoint): # Get first the ETag _response = self.client.get(f"/{STAC_BASE_V}/{endpoint}") self.assertStatusCode(200, _response) # The ETag change between each test call due to the created, # updated time that are in the hash computation of the ETag self.check_header_etag(None, _response) return _response['ETag'] def test_get_precondition(self): for endpoint in [ f'collections/{self.collection["name"]}', f'collections/{self.collection["name"]}/items/{self.item["name"]}', f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}' ]: with self.subTest(endpoint=endpoint): response1 = self.client.get(f"/{STAC_BASE_V}/{endpoint}") self.assertStatusCode(200, response1) # The ETag change between each test call due to the created, updated time that are # in the hash computation of the ETag self.check_header_etag(None, response1) response2 = self.client.get( f"/{STAC_BASE_V}/{endpoint}", HTTP_IF_NONE_MATCH=response1['ETag']) self.assertEqual(response1['ETag'], response2['ETag']) self.assertStatusCode(304, response2) response3 = self.client.get(f"/{STAC_BASE_V}/{endpoint}", HTTP_IF_MATCH=response1['ETag']) self.assertEqual(response1['ETag'], response3['ETag']) self.assertStatusCode(200, response3) response4 = self.client.get(f"/{STAC_BASE_V}/{endpoint}", HTTP_IF_MATCH='"abcd"') self.assertStatusCode(412, response4) def test_put_precondition(self): client_login(self.client) for (endpoint, sample) in [ (f'collections/{self.collection["name"]}', self.factory.create_collection_sample( name=self.collection["name"], sample='collection-2', )), (f'collections/{self.collection["name"]}/items/{self.item["name"]}', self.factory.create_item_sample( collection=self.collection.model, name=self.item["name"], sample='item-2', )), (f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}', self.factory.create_asset_sample( item=self.item.model, name=self.asset["name"], sample='asset-1-updated', media_type=self.asset['media_type'], checksum_multihash=self.asset["checksum_multihash"])), ]: with self.subTest(endpoint=endpoint): response = self.client.put(f"/{STAC_BASE_V}/{endpoint}", sample.get_json('put'), content_type="application/json", HTTP_IF_MATCH='"abc"') self.assertStatusCode(412, response) response = self.client.put( f"/{STAC_BASE_V}/{endpoint}", sample.get_json('put'), content_type="application/json", HTTP_IF_MATCH=self.get_etag(endpoint)) self.assertStatusCode(200, response) def test_wrong_media_type(self): client_login(self.client) for (request_methods, endpoint, data) in [ ( ['put', 'patch'], f'collections/{self.collection["name"]}', {}, ), ( ['put', 'patch'], f'collections/{self.collection["name"]}/items/{self.item["name"]}', {}, ), (['post'], 'search', { "query": { "title": { "eq": "My item 1" } } }), ]: with self.subTest(endpoint=endpoint): client_requests = [ getattr(self.client, method) for method in request_methods ] for client_request in client_requests: response = client_request(f"/{STAC_BASE_V}/{endpoint}", data=data, content_type="plain/text") self.assertStatusCode(415, response) def test_patch_precondition(self): client_login(self.client) for (endpoint, data) in [ ( f'collections/{self.collection["name"]}', { 'title': 'New title patched' }, ), ( f'collections/{self.collection["name"]}/items/{self.item["name"]}', { 'properties': { 'title': 'New title patched' } }, ), ( f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}', { 'title': 'New title patched' }, ), ]: with self.subTest(endpoint=endpoint): response = self.client.patch(f"/{STAC_BASE_V}/{endpoint}", data, content_type="application/json", HTTP_IF_MATCH='"abc"') self.assertStatusCode(412, response) response = self.client.patch( f"/{STAC_BASE_V}/{endpoint}", data, content_type="application/json", HTTP_IF_MATCH=self.get_etag(endpoint)) self.assertStatusCode(200, response) def test_delete_precondition(self): client_login(self.client) for endpoint in [ f'collections/{self.collection["name"]}/items/{self.item["name"]}' f'/assets/{self.asset["name"]}', f'collections/{self.collection["name"]}/items/{self.item["name"]}', # f'collections/{self.collection["name"]}', ]: with self.subTest(endpoint=endpoint): etag1 = self.get_etag(endpoint) response = self.client.delete(f"/{STAC_BASE_V}/{endpoint}", content_type="application/json", HTTP_IF_MATCH='"abc"') self.assertStatusCode( 412, response, msg='Request should be refused due to precondition failed') response = self.client.delete(f"/{STAC_BASE_V}/{endpoint}", content_type="application/json", HTTP_IF_MATCH=etag1) self.assertStatusCode(200, response)
class AssetsEndpointTestCase(StacBaseTestCase): @mock_s3_asset_file def setUp(self): # pylint: disable=invalid-name self.client = Client() self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection).model self.asset_1 = self.factory.create_asset_sample(item=self.item, db_create=True) self.maxDiff = None # pylint: disable=invalid-name def test_assets_endpoint(self): collection_name = self.collection.name item_name = self.item.name asset_2 = self.factory.create_asset_sample(item=self.item, sample='asset-2', db_create=True) asset_3 = self.factory.create_asset_sample(item=self.item, sample='asset-3', db_create=True) response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets" ) self.assertStatusCode(200, response) json_data = response.json() logger.debug('Response (%s):\n%s', type(json_data), pformat(json_data)) self.assertIn('assets', json_data, msg='assets is missing in response') self.assertEqual(3, len(json_data['assets']), msg='Number of assets doen\'t match the expected') for i, asset in enumerate([self.asset_1, asset_2, asset_3]): self.check_stac_asset(asset.json, json_data['assets'][i], collection_name, item_name, ignore=['item']) def test_assets_endpoint_collection_does_not_exist(self): collection_name = "non-existent" item_name = self.item.name response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets" ) self.assertStatusCode(404, response) def test_assets_endpoint_item_does_not_exist(self): collection_name = self.collection.name item_name = "non-existent" response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets" ) self.assertStatusCode(404, response) def test_single_asset_endpoint(self): collection_name = self.collection.name item_name = self.item.name asset_name = self.asset_1["name"] response = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}/assets/{asset_name}" ) json_data = response.json() self.assertStatusCode(200, response) logger.debug('Response (%s):\n%s', type(json_data), pformat(json_data)) self.check_stac_asset(self.asset_1.json, json_data, collection_name, item_name, ignore=['item']) # The ETag change between each test call due to the created, updated time that are in the # hash computation of the ETag self.check_header_etag(None, response)