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 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
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 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 CollectionsUnImplementedEndpointTestCase(StacBaseTestCase): def setUp(self): # pylint: disable=invalid-name self.client = Client() client_login(self.client) self.factory = Factory() self.collection = self.factory.create_collection_sample() self.maxDiff = None # pylint: disable=invalid-name def test_collections_post_unimplemented(self): response = self.client.post( f"/{STAC_BASE_V}/collections", data=self.collection.get_json('post'), content_type='application/json' ) self.assertStatusCode(405, response)
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 CollectionsDeleteEndpointTestCase(StacBaseTestCase): def setUp(self): # pylint: disable=invalid-name self.client = Client() client_login(self.client) self.factory = Factory() self.collection = self.factory.create_collection_sample(db_create=True) self.item = self.factory.create_item_sample(self.collection.model, db_create=True) self.maxDiff = None # pylint: disable=invalid-name def test_authorized_collection_delete(self): path = reverse('collection-detail', args=[self.collection["name"]]) response = self.client.delete(path) self.assertStatusCode(400, response) self.assertEqual( response.json()['description'], ['Deleting Collection with items not allowed'] ) # delete first the item item_path = reverse('item-detail', args=[self.collection["name"], self.item['name']]) response = self.client.delete(item_path) self.assertStatusCode(200, response) # try the collection delete again response = self.client.delete(path) self.assertStatusCode(200, response) # Check that the object doesn't exists anymore self.assertFalse( CollectionLink.objects.filter(collection__name=self.collection["name"]).exists(), msg="Deleted collection link still in DB" ) self.assertFalse( Provider.objects.filter(collection__name=self.collection["name"]).exists(), msg="Deleted provider still in DB" ) self.assertFalse( Collection.objects.filter(name=self.collection["name"]).exists(), msg="Deleted collection still in DB" )
def test_validate_asset_href_path(self): factory = Factory() collection = factory.create_collection_sample( name='collection-test').model item = factory.create_item_sample(collection=collection, name='item-test').model validate_asset_href_path(item, 'asset-test', 'collection-test/item-test/asset-test') with self.settings(AWS_S3_CUSTOM_DOMAIN='new-domain'): validate_asset_href_path(item, 'asset-test', 'collection-test/item-test/asset-test') with self.settings(AWS_S3_CUSTOM_DOMAIN='new-domain/with-prefix/'): validate_asset_href_path( item, 'asset-test', 'with-prefix/collection-test/item-test/asset-test') with self.settings(AWS_S3_CUSTOM_DOMAIN='//new-domain/with-prefix'): validate_asset_href_path( item, 'asset-test', 'with-prefix/collection-test/item-test/asset-test') with self.settings(AWS_S3_CUSTOM_DOMAIN='//new domain/with-prefix'): validate_asset_href_path( item, 'asset-test', 'with-prefix/collection-test/item-test/asset-test') with self.assertRaises( serializers.ValidationError, msg= "Invalid Asset href path did not raises serializers.ValidationError" ): validate_asset_href_path(item, 'asset-test', 'asset-test') validate_asset_href_path(item, 'asset-test', 'item-test/asset-test') validate_asset_href_path(item, 'asset-test', 'collection-test/item-test/asset-test') validate_asset_href_path( item, 'asset-test', '/service-stac-local/collection-test/item-test/asset-test')
class OneItemSpatialTestCase(StacBaseTestCase): def setUp(self): self.client = Client() client_login(self.client) self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.items = self.factory.create_item_samples( ['item-switzerland-west'], self.collection, db_create=True) def test_single_item(self): collection_name = self.collection.name item_name = self.items[0].model.name response_item = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}") response_item_json = response_item.json() response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") response_collection_json = response_collection.json() bbox_collection = response_collection_json['extent']['spatial'][ 'bbox'][0] bbox_items = response_item_json['bbox'] self.assertEqual(bbox_items, bbox_collection) def test_no_items(self): collection_name = self.collection.name item_name = self.items[0].model.name # delete the item path = f'/{STAC_BASE_V}/collections/{self.collection.name}/items/{item_name}' response = self.client.delete(path) self.assertStatusCode(200, response) response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") response_collection_json = response_collection.json() bbox_collection = response_collection_json['extent']['spatial'][ 'bbox'][0] self.assertEqual(bbox_collection, [])
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 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 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 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 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 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 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 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 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 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 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)
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 TwoItemsSpatialTestCase(StacBaseTestCase): def setUp(self): self.client = Client() client_login(self.client) self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.items = self.factory.create_item_samples( ['item-switzerland-west', 'item-switzerland-east'], self.collection, name=['item-switzerland-west', 'item-switzerland-east'], db_create=True, ) def test_two_item_endpoint(self): collection_name = self.collection.name item_west = self.items[0].model.name response_item_west = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_west}") item_east = self.items[1].model.name response_item_east = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_east}") response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") response_collection_json = response_collection.json() bbox_collection = response_collection.json( )['extent']['spatial']['bbox'][0] bbox_item_west = response_item_west.json()['bbox'] bbox_item_east = response_item_east.json()['bbox'] self.assertNotEqual( bbox_item_west, bbox_item_east, msg= 'the bbox should not be the same. one should cover the east, the other the west' ) self.assertNotEqual( bbox_item_west, bbox_collection, msg='the item bbox should be within the collection bbox') self.assertNotEqual( bbox_item_east, bbox_collection, msg='the item bbox should be within the collection bbox') polygon_west = Polygon.from_bbox(bbox_item_west) polygon_east = Polygon.from_bbox(bbox_item_east) union_polygon = Polygon.from_bbox( self._round_list(polygon_west.union(polygon_east).extent)) collection_polygon = Polygon.from_bbox( self._round_list(bbox_collection)) self.assertEqual( collection_polygon, union_polygon, msg= 'the union of item east and item west should be identical with the collection' ) def test_one_left_item(self): collection_name = self.collection.name item_west = self.items[0].model.name item_east = self.items[1].model.name # delete the eastern item path = f'/{STAC_BASE_V}/collections/{self.collection.name}/items/{item_east}' response = self.client.delete(path) self.assertStatusCode(200, response) response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") bbox_collection = response_collection.json( )['extent']['spatial']['bbox'][0] response_item_west = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_west}") bbox_item_west = response_item_west.json()['bbox'] self.assertEqual( self._round_list(bbox_collection), self._round_list(bbox_item_west), msg='the item and the collection bbox should be congruent') def test_update_covering_item(self): collection_name = self.collection.name item_name = self.items[0].model.name sample = self.factory.create_item_sample( self.collection, sample='item-covers-switzerland', name=item_name) path = f'/{STAC_BASE_V}/collections/{self.collection.name}/items/{item_name}' response = self.client.put(path, data=sample.get_json('put'), content_type="application/json") response_item = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}/items/{item_name}") bbox_item = response_item.json()['bbox'] response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") bbox_collection = response_collection.json( )['extent']['spatial']['bbox'][0] self.assertEqual( self._round_list(bbox_collection), self._round_list(bbox_item), msg='the item and the collection bbox should be congruent') def test_add_covering_item(self): collection_name = self.collection.name response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") bbox_collection_ch = response_collection.json( )['extent']['spatial']['bbox'][0] sample = self.factory.create_item_sample( self.collection, sample='item-covers-switzerland', db_create=True).model response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") bbox_collection_covers_ch = response_collection.json( )['extent']['spatial']['bbox'][0] self.assertNotEqual( self._round_list(bbox_collection_ch), self._round_list(bbox_collection_covers_ch), msg= 'the bbox that covers switzerland should be different from the bbox of ch' ) polygon_ch = Polygon.from_bbox(bbox_collection_ch) polygon_covers_ch = Polygon.from_bbox(bbox_collection_covers_ch) self.assertGreater( polygon_covers_ch.area, polygon_ch.area, msg= 'the area of the polygon covering CH has to be bigger than the bbox CH' ) def test_add_another_item(self): collection_name = self.collection.name response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") bbox_collection_ch = response_collection.json( )['extent']['spatial']['bbox'][0] sample = self.factory.create_item_sample(self.collection, sample='item-paris', db_create=True).model response_collection = self.client.get( f"/{STAC_BASE_V}/collections/{collection_name}") bbox_collection_paris = response_collection.json( )['extent']['spatial']['bbox'][0] self.assertNotEqual(self._round_list(bbox_collection_ch), self._round_list(bbox_collection_paris), msg='the bbox should have changed meanwhile') polygon_ch = Polygon.from_bbox(bbox_collection_ch) polygon_paris = Polygon.from_bbox(bbox_collection_paris) self.assertGreater( polygon_paris.area, polygon_ch.area, msg= 'the area of the bbox up to Paris has to be bigger than the bbox of Switzerland' ) def _round_list(self, unrounded_list): '''round a list of numbers Args: unrounded_list: list(float) Returns: list A list of rounded numbers ''' rounded_list = [round(i, 5) for i in unrounded_list] return rounded_list
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 CollectionsCreateEndpointTestCase(StacBaseTestCase): def setUp(self): # pylint: disable=invalid-name self.client = Client() client_login(self.client) self.factory = Factory() self.collection = self.factory.create_collection_sample() self.maxDiff = None # pylint: disable=invalid-name def test_collection_upsert_create(self): sample = self.factory.create_collection_sample(sample='collection-2') # the dataset to update does not exist yet response = self.client.put( f"/{STAC_BASE_V}/collections/{sample['name']}", data=sample.get_json('put'), content_type='application/json' ) self.assertStatusCode(201, response) self.check_stac_collection(sample.json, response.json()) def test_invalid_collections_create(self): # the dataset already exists in the database collection = self.factory.create_collection_sample(sample='collection-invalid') response = self.client.put( f"/{STAC_BASE_V}/collections/{collection['name']}", data=collection.get_json('put'), content_type='application/json' ) self.assertStatusCode(400, response) self.assertEqual({'license': ['Not a valid string.']}, response.json()['description'], msg='Unexpected error message') def test_collections_min_mandatory_create(self): # a post with the absolute valid minimum collection = self.factory.create_collection_sample(required_only=True) path = f"/{STAC_BASE_V}/collections/{collection['name']}" response = self.client.put( path, data=collection.get_json('put'), content_type='application/json' ) response_json = response.json() logger.debug(response_json) self.assertStatusCode(201, response) self.check_header_location(f'{path}', response) self.assertNotIn('title', response_json.keys()) # key does not exist self.assertNotIn('providers', response_json.keys()) # key does not exist self.check_stac_collection(collection.json, response_json) def test_collections_less_than_mandatory_create(self): # a post with the absolute valid minimum collection = self.factory.create_collection_sample( sample='collection-missing-mandatory-fields' ) response = self.client.put( f"/{STAC_BASE_V}/collections/{collection['name']}", data=collection.get_json('put'), content_type='application/json' ) self.assertStatusCode(400, response) self.assertEqual( { 'description': ['This field is required.'], 'license': ['This field is required.'], }, response.json()['description'], msg='Unexpected error message', ) def test_collections_create_unpublished(self): published_collection = self.factory.create_collection_sample(db_create=True) published_items = self.factory.create_item_samples( 2, collection=published_collection.model, name=['item-1-1', 'item-1-2'], db_create=True ) collection_sample = self.factory.create_collection_sample(published=False) path = f"/{STAC_BASE_V}/collections/{collection_sample['name']}" response = self.client.put( path, data=collection_sample.get_json('put'), content_type='application/json' ) self.assertStatusCode(201, response) self.check_header_location(f'{path}', response) self.assertNotIn( 'published', response.json(), msg="'published' flag should not be seen in answer" ) collection = Collection.objects.get(name=collection_sample['name']) self.assertFalse( collection.published, msg='Collection marked as published when it shouldn\'t' ) # verify that the collection is not found in the collection list response = self.client.get(f"/{STAC_BASE_V}/collections") self.assertStatusCode(200, response) self.assertEqual( len(response.json()['collections']), 1, msg="The un published collection is part of the collection list" ) self.assertEqual(response.json()['collections'][0]['id'], published_collection['name']) # add some items to the collection items = self.factory.create_item_samples( 2, collection=collection, name=['item-2-1', 'item-2-2'], db_create=True ) # Check that those items are not found in the search endpoint response = self.client.get(f'/{STAC_BASE_V}/search') self.assertStatusCode(200, response) self.assertEqual( len(response.json()['features']), 2, msg="Too many items found, probably the unpublished are also returned" ) for i, item in enumerate(response.json()['features']): self.assertEqual(item['id'], published_items[i]['name']) # Publish the collection response = self.client.patch( f"/{STAC_BASE_V}/collections/{collection.name}", data={'published': True}, content_type='application/json' ) self.assertStatusCode(200, response) # verify that now the collection can be seen response = self.client.get(f"/{STAC_BASE_V}/collections") self.assertStatusCode(200, response) self.assertEqual(len(response.json()['collections']), 2, msg="No enough collections found") self.assertEqual(response.json()['collections'][0]['id'], published_collection.json['id']) self.assertEqual(response.json()['collections'][1]['id'], collection.name) # Check that the items are found in the search endpoint response = self.client.get(f'/{STAC_BASE_V}/search') self.assertStatusCode(200, response) self.assertEqual(len(response.json()['features']), 4, msg="Not all published items found") def test_collection_atomic_upsert_create_500(self): sample = self.factory.create_collection_sample(sample='collection-2') # 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-collection-detail-http-500', args=[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('collection-detail', args=[sample['name']])) self.assertStatusCode(404, response)
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 CollectionSpatialExtentTestCase(TestCase): ''' Testing the propagation of item geometries to the bbox of the collection ''' def setUp(self): self.factory = Factory() self.collection = self.factory.create_collection_sample().model self.item = self.factory.create_item_sample( collection=self.collection, name='base-bbox', geometry=GEOSGeometry( 'SRID=4326;POLYGON ((0 0, 0 45, 45 45, 45 0, 0 0))')).model def test_if_collection_gets_right_extent(self): # the collection has to have the bbox of the item self.assertEqual(self.collection.extent_geometry, self.item.geometry) def test_if_new_collection_has_extent(self): # a new collection has no bbox yet collection_no_bbox = self.factory.create_collection_sample( name='collection-no-bbox').model self.assertIsNone(collection_no_bbox.extent_geometry) def test_changing_bbox_with_bigger_item(self): # changing the size of the bbox of the collection self.assertEqual(self.collection.extent_geometry, self.item.geometry) bigger_item = self.factory.create_item_sample( self.collection, name='bigger-bbox', geometry=GEOSGeometry( 'SRID=4326;POLYGON ((0 0, 0 50, 50 50, 50 0, 0 0))')).model # collection has to have the size of the bigger extent self.assertEqual(self.collection.extent_geometry, bigger_item.geometry) bigger_item.delete() self.assertEqual(self.collection.extent_geometry, self.item.geometry) def test_changing_bbox_with_smaller_item(self): # changing the size of the bbox of the collection self.assertEqual(self.collection.extent_geometry, self.item.geometry) smaller_item = self.factory.create_item_sample( self.collection, name='smaller-bbox', geometry=GEOSGeometry( 'SRID=4326;POLYGON ((1 1, 1 40, 40 40, 40 1, 1 1))')).model # collection has to have the size of the bigger extent self.assertEqual(self.collection.extent_geometry, self.item.geometry) smaller_item.delete() self.assertEqual(self.collection.extent_geometry, self.item.geometry) def test_changing_bbox_with_diagonal_update(self): # changing collection bbox by moving one of two geometries self.assertEqual(self.collection.extent_geometry, self.item.geometry) diagonal_item = self.factory.create_item_sample( self.collection, name='diagonal-bbox', geometry=GEOSGeometry( 'SRID=4326;POLYGON ((45 45, 45 90, 90 90, 90 45, 45 45))') ).model # collection bbox composed of the two diagonal geometries self.assertEqual( GEOSGeometry(self.collection.extent_geometry).extent, GEOSGeometry(Polygon.from_bbox((0, 0, 90, 90))).extent) # moving the second geometry to be on top of the other one diagonal_item.geometry = GEOSGeometry( 'SRID=4326;POLYGON ((0 0, 0 45, 45 45, 45 0, 0 0))') diagonal_item.full_clean() diagonal_item.save() self.assertEqual( GEOSGeometry(self.collection.extent_geometry).extent, GEOSGeometry(Polygon.from_bbox((0, 0, 45, 45))).extent) diagonal_item.delete() self.assertEqual(self.collection.extent_geometry, self.item.geometry) def test_collection_lost_all_items(self): self.item.delete( ) # should be the one and only item of this collection self.assertIsNone(self.collection.extent_geometry)
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 CollectionsModelTemporalExtentTestCase(TestCase): ''' Testing the propagation of item temporal extent to the temporal extent of the collection ''' y100 = utc_aware( datetime.strptime('0100-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) y150 = utc_aware( datetime.strptime('0150-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) y200 = utc_aware( datetime.strptime('0200-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) y250 = utc_aware( datetime.strptime('0250-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')) y8500 = utc_aware( datetime.strptime('8500-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) y9000 = utc_aware( datetime.strptime('9000-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) y9500 = utc_aware( datetime.strptime('9500-01-01T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ')) def setUp(self): self.factory = Factory() self.collection = self.factory.create_collection_sample().model def add_range_item(self, start, end, name): item = self.factory.create_item_sample( self.collection, name=name, sample='item-2', properties_start_datetime=start, properties_end_datetime=end, ) item.create() return item.model def add_single_datetime_item(self, datetime_val, name): item = self.factory.create_item_sample( self.collection, name=name, properties_datetime=datetime_val) return item.model def test_update_temporal_extent_range(self): # Tests if the collection's temporal extent is correctly updated, when # and item with a time range is added. When a second item with earlier # start_ and later end_datetime, tests, if collection's temporal extent # is updated correctly. # create an item with from year 200 to year 8000 y200_y8000 = self.add_range_item(self.y200, self.y8000, 'y200_y8000') # now the collections start_ and end_datetime should be same as # the ones of item earliest_to_latest: self.assertEqual( self.collection.extent_start_datetime, y200_y8000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "based on range of collection's items failed.") self.assertEqual( self.collection.extent_end_datetime, y200_y8000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "based on range of collection's items failed.") # when adding a second item with earlier start and later end_datetime, # collections temporal range should be updated accordingly # create an item with from year 100 to year 9000 y100_y9000 = self.add_range_item(self.y100, self.y9000, 'y100_y9000') self.assertEqual( self.collection.extent_start_datetime, y100_y9000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "based on range of collection's items failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "based on range of collection's items failed.") def test_update_temporal_extent_update_range_bounds_later_start_earlier_end( self): # Tests, if the collection's temporal extent is updated correctly, when # the bounds of the only item are updated separately, so that new start # date is later and new end date earlier. y100_y9000 = self.add_range_item(self.y100, self.y9000, 'y100_y9000') self.assertEqual( self.collection.extent_start_datetime, y100_y9000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "based on range of collection's item failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "based on range of collection's item failed.") y100_y9000.properties_start_datetime = self.y200 y100_y9000.full_clean() y100_y9000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100_y9000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "after only item's start_datetime was updated failed.") y100_y9000.properties_end_datetime = self.y8000 y100_y9000.full_clean() y100_y9000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "after only item's end_datetime was updated failed.") def test_update_temporal_extent_update_range_bounds_earlier_start_later_end( self): # Tests, if the collection's temporal extent is updated correctly, when # the bounds of the only item are updated separately, so that new start # date is earlier and new end date later. y200_y8000 = self.add_range_item(self.y200, self.y8000, 'y200_y8000') self.assertEqual( self.collection.extent_start_datetime, y200_y8000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "based on range of collection's item failed.") self.assertEqual( self.collection.extent_end_datetime, y200_y8000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "based on range of collection's item failed.") y200_y8000.properties_start_datetime = self.y100 y200_y8000.full_clean() y200_y8000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200_y8000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "after only item's start_datetime was updated failed.") y200_y8000.properties_end_datetime = self.y9000 y200_y8000.full_clean() y200_y8000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_end_datetime, y200_y8000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "after only item's end_datetime was updated failed.") def test_update_temporal_extent_update_range_bounds_defining_item(self): # Tests, if the collection's temporal extent is updated correctly, when # the bounds of the item, that defines the collection's bounds are are # updated (first separately, then back again and then both at same time). y200_y8500 = self.add_range_item(self.y200, self.y8500, 'y200_8500') y100_y9500 = self.add_range_item(self.y100, self.y9500, 'y100_y9500') self.assertEqual( self.collection.extent_start_datetime, y100_y9500.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "based on range of collection's oldest item failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9500.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "based on range of collection's latest item failed.") y100_y9500.properties_start_datetime = self.y150 y100_y9500.full_clean() y100_y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100_y9500.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "after item that defined the start_datetime was updated failed.") y100_y9500.properties_end_datetime = self.y9000 y100_y9500.full_clean() y100_y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_end_datetime, y100_y9500.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "after only item's end_datetime was updated failed.") y100_y9500.properties_start_datetime = self.y250 y100_y9500.full_clean() y100_y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200_y8500.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "after item that defined the start_datetime was updated failed.") y100_y9500.properties_end_datetime = self.y8000 y100_y9500.full_clean() y100_y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_end_datetime, y200_y8500.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "after only item's end_datetime was updated failed.") y100_y9500.properties_start_datetime = self.y100 y100_y9500.properties_end_datetime = self.y9500 y100_y9500.full_clean() y100_y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100_y9500.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "after item that defined the start_datetime was updated failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9500.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "after only item's end_datetime was updated failed.") y100_y9500.properties_start_datetime = self.y250 y100_y9500.properties_end_datetime = self.y8000 y100_y9500.full_clean() y100_y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200_y8500.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "after item that defined the start_datetime was updated failed.") self.assertEqual( self.collection.extent_end_datetime, y200_y8500.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "after only item's end_datetime was updated failed.") def test_update_temporal_extent_deletion_range_item(self): # Two items are added to the collection and one is deleted afterwards. # After the deletion, it is checked, that the temporal # extent of the collection is updated accordingly. # create an item with from year 200 to year 8000 y200_y8000 = self.add_range_item(self.y200, self.y8000, 'y200_y8000') # create an item with from year 100 to year 9000 y100_y9000 = self.add_range_item(self.y100, self.y9000, 'y100_y9000') self.assertEqual( self.collection.extent_start_datetime, y100_y9000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) of collection " "based on range of collection's items failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) of collection " "based on range of collection's items failed.") # now delete the one with the earlier start and later end_datetime first: Item.objects.get(pk=y100_y9000.pk).delete() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200_y8000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) after deletion of " "2nd last item failed.") self.assertEqual( self.collection.extent_end_datetime, y200_y8000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) after deletion of " "2nd last item failed.") def test_update_temporal_extent_deletion_last_range_item(self): # An item is added to the collection and deleted again afterwards. # After the deletion, it is checked, that the temporal # extent of the collection is updated accordingly. # create an item with from year 200 to year 8000 y200_y8000 = self.add_range_item(self.y200, self.y8000, 'y200_y8000') self.assertEqual( self.collection.extent_start_datetime, y200_y8000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) after deletion of " "2nd last item failed.") self.assertEqual( self.collection.extent_end_datetime, y200_y8000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) after deletion of " "2nd last item failed.") # now delete the only and hence last item of the collection: Item.objects.get(pk=y200_y8000.pk).delete() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, None, "Updating temporal extent (extent_start_datetime) after deletion of last " "item in collection failed.") self.assertEqual( self.collection.extent_end_datetime, None, "Updating temporal extent (extent_end_datetime) after deletion of last " "item in collection failed.") def test_update_temporal_extent_switch_range_datetime(self): # Two items with start_ and end_datetimes are added. Afterwards they are # updated to no longer have start_ and end_datetimes each has a single # datetime value. Update of the collection's temporal extent is checked. # Finally one item is deleted, so that the collection's temporal extent # should be [last_items_datetime, last_items_datetime] # create an item with from year 200 to year 8000 y200_y8000 = self.add_range_item(self.y200, self.y8000, 'y200_y8000') # create an item with from year 100 to year 9000 y100_y9000 = self.add_range_item(self.y100, self.y9000, 'y100_y9000') y200_y8000.properties_start_datetime = None y200_y8000.properties_end_datetime = None y200_y8000.properties_datetime = self.y200 y200_y8000.full_clean() y200_y8000.save() self.collection.refresh_from_db() y100_y9000.properties_start_datetime = None y100_y9000.properties_end_datetime = None y100_y9000.properties_datetime = self.y9000 y100_y9000.full_clean() y100_y9000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200_y8000.properties_datetime, "Updating temporal extent (extent_start_datetime) after updating " "item from start_ and end_datetime to single datetime value " "failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_datetime, "Updating temporal extent (extent_end_datetime) after updating " "item from start_ and end_datetime to single datetime value " "failed.") Item.objects.get(pk=y200_y8000.pk).delete() self.collection.refresh_from_db() # refresh collection from db self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100_y9000.properties_datetime, "Updating temporal extent (extent_start_datetime) based on a " "single item's datetime property failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_datetime, "Updating temporal extent (extent_end_datetime) based on a " "single item's datetime property failed.") def test_update_temporal_extent_datetime(self): # Tests if the collection's temporal extent is correctly updated, when # and item with a single datetime value is added. When a second item # with earlier start_datetime is added, it is checked, if collection's # temporal extent is updated correctly. Analogue for adding a third item # with later end_datetime y200 = self.add_single_datetime_item(self.y200, 'y200') self.assertEqual( self.collection.extent_start_datetime, y200.properties_datetime, "Updating temporal extent (extent_start_datetime) based on a " "single item's datetime property failed.") self.assertEqual( self.collection.extent_end_datetime, y200.properties_datetime, "Updating temporal extent (extent_end_datetime) based on a " "single item's datetime property failed.") y100 = self.add_single_datetime_item(self.y100, 'y100') self.assertEqual( self.collection.extent_start_datetime, y100.properties_datetime, "Updating temporal extent (extent_start_datetime) after adding " "a second item with singe datetime property failed.") y8000 = self.add_single_datetime_item(self.y8000, 'y8000') self.assertEqual( self.collection.extent_end_datetime, y8000.properties_datetime, "Updating temporal extent (extent_end_datetime) after adding " "a third item with singe datetime property failed.") def test_update_temporal_extent_update_datetime_property(self): # Test if the collection's temporal extent is updated correctly, when the # datetime value of the only existing item is updated. y8000 = self.add_single_datetime_item(self.y8000, 'y8000') self.assertEqual( self.collection.extent_start_datetime, y8000.properties_datetime, "Updating temporal extent (extent_start_datetime) based on the " "only item's datetime.") self.assertEqual( self.collection.extent_end_datetime, y8000.properties_datetime, "Updating temporal extent (extent_start_datetime) based on the " "only item's datetime.") y8000.properties_datetime = self.y100 y8000.full_clean() y8000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y8000.properties_datetime, "Updating temporal extent (extent_start_datetime) after datetime " "of the only item was updated.") self.assertEqual( self.collection.extent_end_datetime, y8000.properties_datetime, "Updating temporal extent (extent_start_datetime) after datetime " "of the only item was updated.") def test_update_temporal_extent_update_datetime_property_defining_item( self): # Test if the collection's temporal extent is updated correctly, when the # datetime value of the item is updated, that defines a bound of the # collection's temporal extent. y100 = self.add_single_datetime_item(self.y200, 'y100') y200 = self.add_single_datetime_item(self.y200, 'y200') y8500 = self.add_single_datetime_item(self.y8500, 'y8500') y9500 = self.add_single_datetime_item(self.y9500, 'y9500') self.assertEqual( self.collection.extent_start_datetime, y100.properties_datetime, "Updating temporal extent (extent_start_datetime) based on the " "oldest item's datetime.") self.assertEqual( self.collection.extent_end_datetime, y9500.properties_datetime, "Updating temporal extent (extent_end_datetime) based on the " "latest item's datetime.") y9500.properties_datetime = self.y9000 y9500.full_clean() y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_end_datetime, y9500.properties_datetime, "Updating temporal extent (extent_start_datetime) after datetime " "of the latest item was updated.") y100.properties_datetime = self.y150 y100.full_clean() y100.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100.properties_datetime, "Updating temporal extent (extent_start_datetime) after datetime " "of the oldest item was updated.") y9500.properties_datetime = self.y8000 y9500.full_clean() y9500.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_end_datetime, y8500.properties_datetime, "Updating temporal extent (extent_end_datetime) after datetime " "of the latest item was updated.") y100.properties_datetime = self.y250 y100.full_clean() y100.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200.properties_datetime, "Updating temporal extent (extent_start_datetime) after datetime " "of the oldest item was updated.") def test_update_temporal_extent_deletion_older_datetime_item(self): # Two items with single datetime values are added and the older one is # deleted afterwards. It is checked, if the collection's temporal # extent is updated correctly. y200 = self.add_single_datetime_item(self.y200, 'y200') y100 = self.add_single_datetime_item(self.y100, 'y100') # now delete one item: Item.objects.get(pk=y100.pk).delete() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y200.properties_datetime, "Updating temporal extent (extent_start_datetime) after deletion of " "2nd last item failed.") self.assertEqual( self.collection.extent_end_datetime, y200.properties_datetime, "Updating temporal extent (extent_end_datetime) after deletion of " "2nd last item failed.") def test_update_temporal_extent_deletion_younger_datetime_item(self): # Two items with single datetime values are added and the younger one is # deleted afterwards. It is checked, if the collection's temporal # extent is updated correctly. y200 = self.add_single_datetime_item(self.y200, 'y200') y100 = self.add_single_datetime_item(self.y100, 'y100') # now delete one item: Item.objects.get(pk=y200.pk).delete() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100.properties_datetime, "Updating temporal extent (extent_start_datetime) after deletion of " "2nd last item failed.") self.assertEqual( self.collection.extent_end_datetime, y100.properties_datetime, "Updating temporal extent (extent_end_datetime) after deletion of " "2nd last item failed.") def test_update_temporal_extent_deletion_last_datetime_item(self): # An item is added to the collection and deleted again afterwards. # After the deletion, it is checked, that the temporal # extent of the collection is updated accordingly. y200 = self.add_single_datetime_item(self.y200, 'y200') Item.objects.get(pk=y200.pk).delete() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, None, "Updating temporal extent (extent_start_datetime) after deletion of last " "item in collection failed.") self.assertEqual( self.collection.extent_end_datetime, None, "Updating temporal extent (extent_end_datetime) after deletion of last " "item in collection failed.") def test_update_temporal_extent_switch_datetime_range(self): # An item with a single datetime value is added. Afterwards it is updated # to have start_ and end_datetimes instead. Update of the collection's # temporal extent is checked. y200 = self.add_single_datetime_item(self.y200, 'y200') y200.properties_datetime = None y200.properties_start_datetime = self.y100 y200.properties_end_datetime = self.y8000 y200.full_clean() y200.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, self.y100, "Updating temporal extent (extent_start_datetime) after updating " "item from single datetime to a range failed.") self.assertEqual( self.collection.extent_end_datetime, self.y8000, "Updating temporal extent (extent_end_datetime) after updating " "item from single datetime to a range failed.") def test_update_temporal_extent_datetime_mixed_items(self): # Tests, if collection's temporal extent is updated correctly when # mixing items with ranges and single datetime values. y100 = self.add_single_datetime_item(self.y100, 'y100') y9000 = self.add_single_datetime_item(self.y9000, 'y9000') y200_y8000 = self.add_range_item(self.y200, self.y8000, 'y200_y8000') self.assertEqual( self.collection.extent_start_datetime, y100.properties_datetime, "Updating temporal extent (extent_start_datetime) based on mixed " "items failed.") self.assertEqual( self.collection.extent_end_datetime, y9000.properties_datetime, "Updating temporal extent (extent_end_datetime) based on mixed " "items failed.") y100.properties_datetime = self.y200 y100.full_clean() y100.save() y9000.properties_datetime = self.y8000 y9000.full_clean() y9000.save() self.collection.refresh_from_db() self.assertEqual( self.collection.extent_start_datetime, y100.properties_datetime, "Updating temporal extent (extent_start_datetime) based on mixed " "items failed.") self.assertEqual( self.collection.extent_end_datetime, y9000.properties_datetime, "Updating temporal extent (extent_end_datetime) based on mixed " "items failed.") y100_y9000 = self.add_range_item(self.y100, self.y9000, 'y100_y9000') self.assertEqual( self.collection.extent_start_datetime, y100_y9000.properties_start_datetime, "Updating temporal extent (extent_start_datetime) based on mixed " "items failed.") self.assertEqual( self.collection.extent_end_datetime, y100_y9000.properties_end_datetime, "Updating temporal extent (extent_end_datetime) based on mixed " "items failed.")