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'])
Beispiel #14
0
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.")