From 95c0ad54ff45a1549f4bcb6e8dbf454f569fe05e Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 28 Aug 2019 08:14:12 +0200 Subject: [PATCH] WFS3 add ACL and other visibility options --- .../auto_generated/qgsserverapiutils.sip.in | 2 +- .../qgsserverogcapihandler.sip.in | 6 +- src/server/qgsserverapiutils.h | 41 +- src/server/qgsserverogcapihandler.cpp | 6 +- src/server/qgsserverogcapihandler.h | 5 +- src/server/services/wfs3/qgswfs3handlers.cpp | 148 +- src/server/services/wfs3/qgswfs3handlers.h | 34 +- tests/src/python/test_qgsserver_api.py | 45 +- .../api/test_wfs3_api_project.json | 376 ++- .../api/test_wfs3_collections_empty.json | 2 +- ...ions_items_testlayer3_name_eq_tw_star.json | 40 - ...lections_items_testlayer3_name_eq_two.json | 40 - ..._testlayer_with_short_name_eq_tw_star.json | 3 + ...wfs3_collections_items_testlayer_èé.json | 2 +- ...collections_items_testlayer_èé_bbox.json | 2 +- ...ctions_items_testlayer_èé_bbox_3857.json | 2 +- ...ections_items_testlayer_èé_crs_3857.json | 2 +- ...lections_items_testlayer_èé_limit_1.json | 2 +- ...items_testlayer_èé_limit_1_offset_1.json | 2 +- .../api/test_wfs3_collections_project.html | 17 +- .../api/test_wfs3_collections_project.json | 113 +- .../api/test_wfs3_conformance.json | 2 +- .../api/test_wfs3_landing_page.json | 2 +- .../testdata/qgis_server/test_project_api.qgs | 2201 +++++++++++++++++ 24 files changed, 2823 insertions(+), 272 deletions(-) delete mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_tw_star.json delete mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_two.json create mode 100644 tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_tw_star.json create mode 100644 tests/testdata/qgis_server/test_project_api.qgs diff --git a/python/server/auto_generated/qgsserverapiutils.sip.in b/python/server/auto_generated/qgsserverapiutils.sip.in index f1786f97403..01a007b3212 100644 --- a/python/server/auto_generated/qgsserverapiutils.sip.in +++ b/python/server/auto_generated/qgsserverapiutils.sip.in @@ -12,6 +12,7 @@ + class QgsServerApiUtils { %Docstring @@ -57,7 +58,6 @@ This method takes into account the ACL restrictions provided by QGIS Server Acce .. note:: project must not be NULL - TODO: implement ACL %End diff --git a/python/server/auto_generated/qgsserverogcapihandler.sip.in b/python/server/auto_generated/qgsserverogcapihandler.sip.in index 24bd0c929bb..285b4cca53c 100644 --- a/python/server/auto_generated/qgsserverogcapihandler.sip.in +++ b/python/server/auto_generated/qgsserverogcapihandler.sip.in @@ -207,9 +207,11 @@ Fallback to the default content type of the handler if none of the above matches Returns a link to the parent page up to ``levels`` in the HTML hierarchy from the given ``url``, MAP query argument is preserved %End - static QgsVectorLayer *layerFromCollection( const QgsServerApiContext &context, const QString &collectionId ); + static QgsVectorLayer *layerFromCollectionId( const QgsServerApiContext &context, const QString &collectionId ); %Docstring -Returns a vector layer from the ``collectionId`` in the given ``context`` +Returns a vector layer from the ``collectionId`` in the given ``context``. + +:raises QgsServerApiNotFoundError: if the layer could not be found. %End diff --git a/src/server/qgsserverapiutils.h b/src/server/qgsserverapiutils.h index f47162de514..1dbd18a0bca 100644 --- a/src/server/qgsserverapiutils.h +++ b/src/server/qgsserverapiutils.h @@ -25,6 +25,12 @@ #include #include "qgsproject.h" #include "qgsserverprojectutils.h" +#include "qgsserverapicontext.h" + +#ifdef HAVE_SERVER_PYTHON_PLUGINS +#include "qgsaccesscontrol.h" +#include "qgsserverinterface.h" +#endif class QgsRectangle; class QgsCoordinateReferenceSystem; @@ -79,7 +85,6 @@ class SERVER_EXPORT QgsServerApiUtils * This method takes into account the ACL restrictions provided by QGIS Server Access Control plugins. * * \note project must not be NULL - * TODO: implement ACL */ static const QVector publishedWfsLayers( const QgsProject *project ); @@ -92,28 +97,34 @@ class SERVER_EXPORT QgsServerApiUtils * * QVector vectorLayers = publishedLayers(); * - * TODO: implement ACL * \note not available in Python bindings - * \see publishedWfsLayers() */ template - static const QVector publishedWfsLayers( const QgsProject *project ) + static const QVector publishedWfsLayers( const QgsServerApiContext &context ) { - const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); - const QStringList wfstUpdateLayersId = QgsServerProjectUtils::wfstUpdateLayerIds( *project ); - const QStringList wfstInsertLayersId = QgsServerProjectUtils::wfstInsertLayerIds( *project ); - const QStringList wfstDeleteLayersId = QgsServerProjectUtils::wfstDeleteLayerIds( *project ); - QVector result; - const auto constLayers { project->layers() }; - for ( const auto &layer : constLayers ) +#ifdef HAVE_SERVER_PYTHON_PLUGINS + QgsAccessControl *accessControl = context.serverInterface()->accessControls(); +#endif + const QgsProject *project = context.project(); + QVector result; + if ( project ) { - if ( wfstUpdateLayersId.contains( layer->id() ) || - wfstInsertLayersId.contains( layer->id() ) || - wfstDeleteLayersId.contains( layer->id() ) ) + const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + const auto constLayers { project->layers() }; + for ( const auto &layer : constLayers ) { + if ( ! wfsLayerIds.contains( layer->id() ) ) + { + continue; + } +#ifdef HAVE_SERVER_PYTHON_PLUGINS + if ( accessControl && !accessControl->layerReadPermission( layer ) ) + { + continue; + } +#endif result.push_back( layer ); } - } return result; } diff --git a/src/server/qgsserverogcapihandler.cpp b/src/server/qgsserverogcapihandler.cpp index 6064d560dbe..1c48d15e4f2 100644 --- a/src/server/qgsserverogcapihandler.cpp +++ b/src/server/qgsserverogcapihandler.cpp @@ -230,7 +230,7 @@ QgsVectorLayer *QgsServerOgcApiHandler::layerFromContext( const QgsServerApiCont } const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) }; // May throw if not found - return layerFromCollection( context, collectionId ); + return layerFromCollectionId( context, collectionId ); } @@ -463,12 +463,12 @@ QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels ) return result.toString(); } -QgsVectorLayer *QgsServerOgcApiHandler::layerFromCollection( const QgsServerApiContext &context, const QString &collectionId ) +QgsVectorLayer *QgsServerOgcApiHandler::layerFromCollectionId( const QgsServerApiContext &context, const QString &collectionId ) { const auto mapLayers { context.project()->mapLayersByShortName( collectionId ) }; if ( mapLayers.count() != 1 ) { - throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) ); + throw QgsServerApiNotFoundError( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) ); } return mapLayers.first(); } diff --git a/src/server/qgsserverogcapihandler.h b/src/server/qgsserverogcapihandler.h index 5165c5f8774..396805bd6ea 100644 --- a/src/server/qgsserverogcapihandler.h +++ b/src/server/qgsserverogcapihandler.h @@ -314,9 +314,10 @@ class SERVER_EXPORT QgsServerOgcApiHandler static QString parentLink( const QUrl &url, int levels = 1 ); /** - * Returns a vector layer from the \a collectionId in the given \a context + * Returns a vector layer from the \a collectionId in the given \a context. + * \throws QgsServerApiNotFoundError if the layer could not be found. */ - static QgsVectorLayer *layerFromCollection( const QgsServerApiContext &context, const QString &collectionId ); + static QgsVectorLayer *layerFromCollectionId( const QgsServerApiContext &context, const QString &collectionId ); /** * Returns the defaultResponse as JSON diff --git a/src/server/services/wfs3/qgswfs3handlers.cpp b/src/server/services/wfs3/qgswfs3handlers.cpp index ae4c2806d12..e66c9890170 100644 --- a/src/server/services/wfs3/qgswfs3handlers.cpp +++ b/src/server/services/wfs3/qgswfs3handlers.cpp @@ -28,6 +28,12 @@ #include "qgsbufferserverrequest.h" #include "qgsserverprojectutils.h" #include "qgsserverinterface.h" +#include "qgsexpressioncontext.h" +#include "qgsexpressioncontextutils.h" + +#ifdef HAVE_SERVER_PYTHON_PLUGINS +#include "qgsfilterrestorer.h" +#endif #include @@ -187,6 +193,77 @@ json QgsWfs3APIHandler::schema( const QgsServerApiContext &context ) const return data; } +void QgsWfs3AbstractItemsHandler::checkLayerIsAccessible( const QgsVectorLayer *mapLayer, const QgsServerApiContext &context ) const +{ + const QVector publishedLayers = QgsServerApiUtils::publishedWfsLayers( context ); + if ( ! publishedLayers.contains( mapLayer ) ) + { + throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) ); + } +} + +QgsFeatureRequest QgsWfs3AbstractItemsHandler::filteredRequest( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const +{ + QgsFeatureRequest featureRequest; + QgsExpressionContext expressionContext; + expressionContext << QgsExpressionContextUtils::globalScope() + << QgsExpressionContextUtils::projectScope( context.project() ) + << QgsExpressionContextUtils::layerScope( layer ); + + featureRequest.setExpressionContext( expressionContext ); + + //is there alias info for this vector layer? + QMap< int, QString > layerAliasInfo; + const QgsStringMap aliasMap = layer->attributeAliases(); + for ( const auto &aliasKey : aliasMap.keys() ) + { + int attrIndex = layer->fields().lookupField( aliasKey ); + if ( attrIndex != -1 ) + { + layerAliasInfo.insert( attrIndex, aliasIt.value() ); + } + } + + QgsAttributeList attrIndexes = layer->attributeList(); + + // Removed attributes + //excluded attributes for this layer + const QSet &layerExcludedAttributes = layer->excludeAttributesWfs(); + if ( !attrIndexes.isEmpty() && !layerExcludedAttributes.isEmpty() ) + { + const QgsFields &fields = layer->fields(); + for ( const QString &excludedAttribute : layerExcludedAttributes ) + { + int fieldNameIdx = fields.indexOf( excludedAttribute ); + if ( fieldNameIdx > -1 && attrIndexes.contains( fieldNameIdx ) ) + { + attrIndexes.removeOne( fieldNameIdx ); + } + } + } + featureRequest.setSubsetOfAttributes( attrIndexes ); + +#ifdef HAVE_SERVER_PYTHON_PLUGINS + // Python plugins can make further modifications to the allowed attributes + QgsAccessControl *accessControl = context.serverInterface()->accessControls(); + if ( accessControl ) + { + accessControl->filterFeatures( layer, featureRequest ); + + QStringList attributes = QStringList(); + for ( int idx : attrIndexes ) + { + attributes.append( layer->fields().field( idx ).name() ); + } + featureRequest.setSubsetOfAttributes( + accessControl->layerAttributes( layer, attributes ), + layer->fields() ); + } +#endif + + return featureRequest; +} + QgsWfs3LandingPageHandler::QgsWfs3LandingPageHandler() { } @@ -369,12 +446,26 @@ void QgsWfs3CollectionsHandler::handleRequest( const QgsServerApiContext &contex "crs", crss } }; - if ( context.project() ) { - // TODO: include meshes? - for ( const auto &layer : context.project()->layers( ) ) + + const QgsProject *project = context.project(); + const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + for ( const QString &wfsLayerId : wfsLayerIds ) { + const QgsVectorLayer *layer = qobject_cast( project->mapLayer( wfsLayerId ) ); + if ( !layer ) + { + continue; + } + if ( layer->type() != QgsMapLayerType::VectorLayer ) + { + continue; + } + + // Check if the layer is published, raise not found if it is not + checkLayerIsAccessible( layer, context ); + const std::string title { layer->title().isEmpty() ? layer->name().toStdString() : layer->title().toStdString() }; const QString shortName { layer->shortName().isEmpty() ? layer->name() : layer->shortName() }; data["collections"].push_back( @@ -495,9 +586,20 @@ void QgsWfs3DescribeCollectionHandler::handleRequest( const QgsServerApiContext } const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) }; // May throw if not found - const QgsVectorLayer *mapLayer { layerFromCollection( context, collectionId ) }; + const QgsVectorLayer *mapLayer { layerFromCollectionId( context, collectionId ) }; Q_ASSERT( mapLayer ); + + const QgsProject *project = context.project(); + const QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project ); + if ( ! wfsLayerIds.contains( mapLayer->id() ) ) + { + throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) ); + } + + // Check if the layer is published, raise not found if it is not + checkLayerIsAccessible( mapLayer, context ); + const std::string title { mapLayer->title().isEmpty() ? mapLayer->name().toStdString() : mapLayer->title().toStdString() }; const QString shortName { mapLayer->shortName().isEmpty() ? mapLayer->name() : mapLayer->shortName() }; json linksList = links( context ); @@ -561,7 +663,7 @@ json QgsWfs3DescribeCollectionHandler::schema( const QgsServerApiContext &contex json data; Q_ASSERT( context.project() ); - const auto layers { QgsServerApiUtils::publishedWfsLayers( context.project() ) }; + const auto layers { QgsServerApiUtils::publishedWfsLayers( context ) }; // Construct the context with collection id for ( const auto &mapLayer : layers ) { @@ -722,7 +824,7 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context json data; Q_ASSERT( context.project() ); - const auto layers { QgsServerApiUtils::publishedWfsLayers( context.project() ) }; + const QVector layers { QgsServerApiUtils::publishedWfsLayers( context ) }; // Construct the context with collection id for ( const auto &mapLayer : layers ) { @@ -836,6 +938,10 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c } QgsVectorLayer *mapLayer { layerFromContext( context ) }; Q_ASSERT( mapLayer ); + + // Check if the layer is published, raise not found if it is not + checkLayerIsAccessible( mapLayer, context ); + const std::string title { mapLayer->title().isEmpty() ? mapLayer->name().toStdString() : mapLayer->title().toStdString() }; const QString shortName { mapLayer->shortName().isEmpty() ? mapLayer->name() : mapLayer->shortName() }; @@ -1063,27 +1169,49 @@ void QgsWfs3CollectionsFeatureHandler::handleRequest( const QgsServerApiContext { throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Project is invalid or undefined" ) ); } + // Check collectionId const QRegularExpressionMatch match { path().match( context.request()->url().path( ) ) }; if ( ! match.hasMatch() ) { throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) ); } + const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) }; // May throw if not found - QgsVectorLayer *mapLayer { layerFromCollection( context, collectionId ) }; + QgsVectorLayer *mapLayer { layerFromCollectionId( context, collectionId ) }; Q_ASSERT( mapLayer ); + + // Check if the layer is published, raise not found if it is not + checkLayerIsAccessible( mapLayer, context ); + const std::string title { mapLayer->title().isEmpty() ? mapLayer->name().toStdString() : mapLayer->title().toStdString() }; if ( context.request()->method() == QgsServerRequest::Method::GetMethod ) { const QString featureId { match.captured( QStringLiteral( "featureId" ) ) }; QgsJsonExporter exporter { mapLayer }; - const QgsFeature feature { mapLayer->getFeature( featureId.toLongLong() ) }; - if ( ! feature.isValid() ) + +#ifdef HAVE_SERVER_PYTHON_PLUGINS + QgsAccessControl *accessControl = context.serverInterface()->accessControls(); + //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope + //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually + std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() ); + if ( accessControl ) + { + QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, mapLayer, filterRestorer->originalFilters() ); + } +#endif + + QgsFeatureRequest featureRequest = filteredRequest( mapLayer, context ); + featureRequest.setFilterFid( featureId.toLongLong() ); + QgsFeature feature; + QgsFeatureIterator it { mapLayer->getFeatures( featureRequest ) }; + if ( ! it.nextFeature( feature ) && feature.isValid() ) { QgsServerApiInternalServerError( QStringLiteral( "Invalid feature [%1]" ).arg( featureId ) ); } + json data = exporter.exportFeatureToJsonObject( feature ); data["links"] = links( context ); json navigation = json::array(); @@ -1114,7 +1242,7 @@ json QgsWfs3CollectionsFeatureHandler::schema( const QgsServerApiContext &contex json data; Q_ASSERT( context.project() ); - const auto layers { QgsServerApiUtils::publishedWfsLayers( context.project() ) }; + const auto layers { QgsServerApiUtils::publishedWfsLayers( context ) }; // Construct the context with collection id for ( const auto &mapLayer : layers ) { diff --git a/src/server/services/wfs3/qgswfs3handlers.h b/src/server/services/wfs3/qgswfs3handlers.h index 14ace01c74f..7ac6e27608d 100644 --- a/src/server/services/wfs3/qgswfs3handlers.h +++ b/src/server/services/wfs3/qgswfs3handlers.h @@ -20,12 +20,35 @@ #include "qgsserverogcapihandler.h" +class QgsFeatureRequest; class QgsServerOgcApi; +/** + * The QgsWfs3AbstractItemsHandler class provides some + * functionality which is common to the handlers that + * return items. + */ +class QgsWfs3AbstractItemsHandler: public QgsServerOgcApiHandler +{ + public: + + /** + * Checks if the layer is published in WFS (and perform additional checks for access + * control if plugins are enabled) + * and throws an exception if it is not. + * \param layer the map layer + * \param context the server api context + * \throws QgsServerApiNotFoundException if the layer is NOT published + */ + void checkLayerIsAccessible( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const; + + QgsFeatureRequest filteredRequest( const QgsVectorLayer *layer, const QgsServerApiContext &context ) const; +}; + /** * The APIHandler class Wfs3handles the API definition */ -class QgsWfs3APIHandler: public QgsServerOgcApiHandler +class QgsWfs3APIHandler: public QgsWfs3AbstractItemsHandler { public: @@ -44,6 +67,7 @@ class QgsWfs3APIHandler: public QgsServerOgcApiHandler private: const QgsServerOgcApi *mApi = nullptr; + }; @@ -125,7 +149,7 @@ class QgsWfs3ConformanceHandler: public QgsServerOgcApiHandler * The CollectionsHandler lists all available collections for the current project * Path: /collections */ -class QgsWfs3CollectionsHandler: public QgsServerOgcApiHandler +class QgsWfs3CollectionsHandler: public QgsWfs3AbstractItemsHandler { public: @@ -155,7 +179,7 @@ class QgsWfs3CollectionsHandler: public QgsServerOgcApiHandler * The DescribeCollectionHandler describes a single collection * Path: /collections/{collectionId} */ -class QgsWfs3DescribeCollectionHandler: public QgsServerOgcApiHandler +class QgsWfs3DescribeCollectionHandler: public QgsWfs3AbstractItemsHandler { public: QgsWfs3DescribeCollectionHandler( ); @@ -175,7 +199,7 @@ class QgsWfs3DescribeCollectionHandler: public QgsServerOgcApiHandler * The CollectionsItemsHandler list all items in the collection * Path: /collections/{collectionId} */ -class QgsWfs3CollectionsItemsHandler: public QgsServerOgcApiHandler +class QgsWfs3CollectionsItemsHandler: public QgsWfs3AbstractItemsHandler { public: QgsWfs3CollectionsItemsHandler( ); @@ -204,7 +228,7 @@ class QgsWfs3CollectionsItemsHandler: public QgsServerOgcApiHandler }; -class QgsWfs3CollectionsFeatureHandler: public QgsServerOgcApiHandler +class QgsWfs3CollectionsFeatureHandler: public QgsWfs3AbstractItemsHandler { public: QgsWfs3CollectionsFeatureHandler( ); diff --git a/tests/src/python/test_qgsserver_api.py b/tests/src/python/test_qgsserver_api.py index 9c1a3003c20..9ba46fec24c 100644 --- a/tests/src/python/test_qgsserver_api.py +++ b/tests/src/python/test_qgsserver_api.py @@ -70,7 +70,7 @@ class QgsServerAPIUtilsTest(QgsServerTestBase): """Test published WMS CRSs""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') crss = QgsServerApiUtils.publishedCrsList(project) self.assertTrue('http://www.opengis.net/def/crs/OGC/1.3/CRS84' in crss) self.assertTrue('http://www.opengis.net/def/crs/EPSG/9.6.2/3857' in crss) @@ -118,7 +118,7 @@ class QgsServerAPITestBase(QgsServerTestBase): """ QGIS API server tests""" # Set to True in child classes to re-generate reference files for this class - regeregenerate_api_reference = False + regeregenerate_api_reference = True def dump(self, response): """Returns the response body as str""" @@ -282,7 +282,7 @@ class QgsServerAPITest(QgsServerAPITestBase): request = QgsBufferServerRequest('http://server.qgis.org/wfs3/api.openapi3') project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') self.compareApi(request, project, 'test_wfs3_api_project.json') def test_wfs3_conformance(self): @@ -304,14 +304,14 @@ class QgsServerAPITest(QgsServerAPITestBase): """Test WFS3 API collections in json format""" request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections.json') project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') self.compareApi(request, project, 'test_wfs3_collections_project.json') def test_wfs3_collections_html(self): """Test WFS3 API collections in html format""" request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections.html') project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') self.compareApi(request, project, 'test_wfs3_collections_project.html') def test_wfs3_collections_content_type(self): @@ -320,7 +320,7 @@ class QgsServerAPITest(QgsServerAPITestBase): request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections') request.setHeader('Accept', 'text/html') project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') response = QgsBufferServerResponse() self.server.handleRequest(request, response, project) self.assertEqual(response.headers()['Content-Type'], 'text/html') @@ -328,14 +328,14 @@ class QgsServerAPITest(QgsServerAPITestBase): def test_wfs3_collection_items(self): """Test WFS3 API items""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items') self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé.json') def test_wfs3_collection_items_crs(self): """Test WFS3 API items with CRS""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') encoded_crs = parse.quote('http://www.opengis.net/def/crs/EPSG/9.6.2/3857', safe='') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?crs={}'.format(encoded_crs)) self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_crs_3857.json') @@ -343,7 +343,7 @@ class QgsServerAPITest(QgsServerAPITestBase): def test_invalid_args(self): """Test wrong args""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=-1') response = QgsBufferServerResponse() self.server.handleRequest(request, response, project) @@ -359,14 +359,14 @@ class QgsServerAPITest(QgsServerAPITestBase): def test_wfs3_collection_items_limit(self): """Test WFS3 API item limits""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=1') self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_limit_1.json') def test_wfs3_collection_items_limit_offset(self): """Test WFS3 API offset""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=1&offset=1') self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_limit_1_offset_1.json') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=1&offset=-1') @@ -383,7 +383,7 @@ class QgsServerAPITest(QgsServerAPITestBase): def test_wfs3_collection_items_bbox(self): """Test WFS3 API bbox""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?bbox=8.203495,44.901482,8.203497,44.901484') self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_èé_bbox.json') @@ -411,16 +411,23 @@ class QgsServerAPITest(QgsServerAPITestBase): def test_wfs3_field_filters(self): """Test field filters""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') + # Check not published + response = QgsBufferServerResponse() request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer3/items?name=two') - self.compareApi(request, project, 'test_wfs3_collections_items_testlayer3_name_eq_two.json') + self.server.handleRequest(request, response, project) + self.assertEqual(response.statusCode(), 404) # Not found + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/layer_with_short_name/items?name=two') + self.server.handleRequest(request, response, project) + self.assertEqual(response.statusCode(), 200) # Bad request + self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_with_short_name_eq_two.json') def test_wfs3_field_filters_star(self): """Test field filters""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') - request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer3/items?name=tw*') - self.compareApi(request, project, 'test_wfs3_collections_items_testlayer3_name_eq_tw_star.json') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') + request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer_with_short_name/items?name=tw*') + self.compareApi(request, project, 'test_wfs3_collections_items_testlayer_with_short_name_eq_tw_star.json') class Handler1(QgsServerOgcApiHandler): @@ -552,7 +559,7 @@ class QgsServerOgcAPITest(QgsServerAPITestBase): """Test OGC API Handler""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé/items?limit=-1') response = QgsBufferServerResponse() @@ -632,7 +639,7 @@ class QgsServerOgcAPITest(QgsServerAPITestBase): """Test OGC API Handler content types""" project = QgsProject() - project.read(unitTestDataPath('qgis_server') + '/test_project.qgs') + project.read(unitTestDataPath('qgis_server') + '/test_project_api.qgs') request = QgsBufferServerRequest('http://server.qgis.org/api3/handlerthree?value1=9.5') response = QgsBufferServerResponse() diff --git a/tests/testdata/qgis_server/api/test_wfs3_api_project.json b/tests/testdata/qgis_server/api/test_wfs3_api_project.json index 60258e96dd0..10d85023b44 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_api_project.json +++ b/tests/testdata/qgis_server/api/test_wfs3_api_project.json @@ -584,6 +584,200 @@ Content-Type: application/openapi+json;version=3.0 "tags": "Capabilities" } }, + "/wfs3/collections/exclude_attribute/items": { + "get": { + "description": "Every feature in a dataset belongs to a collection. A dataset may consist of multiple feature collections. A feature collection is often a collection of features of a similar type, based on a common schema. Use content negotiation or specify a file extension to request HTML (.html) or GeoJSON (.json).", + "operationId": "getFeatures_testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de", + "parameters": [ + [ + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/offset" + }, + { + "$ref": "#/components/parameters/resultType" + }, + { + "$ref": "#/components/parameters/bbox" + }, + { + "$ref": "#/components/parameters/bbox-crs" + } + ], + { + "description": "Filter the collection by 'id'", + "explode": false, + "in": "query", + "name": "id", + "required": false, + "schema": { + "type": "integer" + }, + "style": "form" + }, + { + "description": "Filter the collection by 'name'", + "explode": false, + "in": "query", + "name": "name", + "required": false, + "schema": { + "type": "string" + }, + "style": "form" + }, + { + "description": "Filter the collection by 'utf8nameè'", + "explode": false, + "in": "query", + "name": "utf8nameè", + "required": false, + "schema": { + "type": "string" + }, + "style": "form" + } + ], + "responses": [ + [ + "200", + { + "content": { + "application/geo+json": { + "schema": { + "$ref": "#/components/schemas/featureCollectionGeoJSON" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Metadata about the collection 'A test vector layer' shared by this API." + } + ], + { + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/exception" + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "description": "An error occurred." + } + } + ], + "summary": "Retrieve features of 'A test vector layer' feature collection", + "tags": "Features" + } + }, + "/wfs3/collections/short-name/items": { + "get": { + "description": "Every feature in a dataset belongs to a collection. A dataset may consist of multiple feature collections. A feature collection is often a collection of features of a similar type, based on a common schema. Use content negotiation or specify a file extension to request HTML (.html) or GeoJSON (.json).", + "operationId": "getFeatures_testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774", + "parameters": [ + [ + { + "$ref": "#/components/parameters/limit" + }, + { + "$ref": "#/components/parameters/offset" + }, + { + "$ref": "#/components/parameters/resultType" + }, + { + "$ref": "#/components/parameters/bbox" + }, + { + "$ref": "#/components/parameters/bbox-crs" + } + ], + { + "description": "Filter the collection by 'id'", + "explode": false, + "in": "query", + "name": "id", + "required": false, + "schema": { + "type": "integer" + }, + "style": "form" + }, + { + "description": "Filter the collection by 'name'", + "explode": false, + "in": "query", + "name": "name", + "required": false, + "schema": { + "type": "string" + }, + "style": "form" + }, + { + "description": "Filter the collection by 'utf8nameè'", + "explode": false, + "in": "query", + "name": "utf8nameè", + "required": false, + "schema": { + "type": "string" + }, + "style": "form" + } + ], + "responses": [ + [ + "200", + { + "content": { + "application/geo+json": { + "schema": { + "$ref": "#/components/schemas/featureCollectionGeoJSON" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Metadata about the collection 'A test vector layer' shared by this API." + } + ], + { + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/exception" + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "description": "An error occurred." + } + } + ], + "summary": "Retrieve features of 'A test vector layer' feature collection", + "tags": "Features" + } + }, "/wfs3/collections/testlayer èé/items": { "get": { "description": "Every feature in a dataset belongs to a collection. A dataset may consist of multiple feature collections. A feature collection is often a collection of features of a similar type, based on a common schema. Use content negotiation or specify a file extension to request HTML (.html) or GeoJSON (.json).", @@ -771,6 +965,186 @@ Content-Type: application/openapi+json;version=3.0 "tags": "Capabilities" } }, + "/wfs3collections/exclude_attribute": { + "get": { + "description": "Metadata about a feature collection.", + "operationId": "describeCollection_testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de", + "responses": [ + [ + "200", + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/collectionInfo" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Metadata about the collection 'A test vector layer' shared by this API." + } + ], + { + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/exception" + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "description": "An error occurred." + } + } + ], + "summary": "Describe the 'A test vector layer' feature collection", + "tags": "Capabilities" + } + }, + "/wfs3collections/exclude_attribute/items/{featureId}": { + "get": { + "description": "Retrieve a feature; use content negotiation or specify a file extension to request HTML (.html or GeoJSON (.json)", + "operationId": "getFeature_testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de", + "responses": [ + [ + "200", + { + "content": { + "application/geo+json": { + "schema": { + "$ref": "#/components/schemas/featureGeoJSON" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Retrieve a 'A test vector layer' feature by 'featureId'." + } + ], + { + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/exception" + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "description": "An error occurred." + } + } + ], + "summary": "Retrieve a single feature from the 'A test vector layer' feature collection", + "tags": "Features" + } + }, + "/wfs3collections/short-name": { + "get": { + "description": "Metadata about a feature collection.", + "operationId": "describeCollection_testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774", + "responses": [ + [ + "200", + { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/collectionInfo" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Metadata about the collection 'A test vector layer' shared by this API." + } + ], + { + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/exception" + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "description": "An error occurred." + } + } + ], + "summary": "Describe the 'A test vector layer' feature collection", + "tags": "Capabilities" + } + }, + "/wfs3collections/short-name/items/{featureId}": { + "get": { + "description": "Retrieve a feature; use content negotiation or specify a file extension to request HTML (.html or GeoJSON (.json)", + "operationId": "getFeature_testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774", + "responses": [ + [ + "200", + { + "content": { + "application/geo+json": { + "schema": { + "$ref": "#/components/schemas/featureGeoJSON" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + }, + "description": "Retrieve a 'A test vector layer' feature by 'featureId'." + } + ], + { + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/exception" + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "description": "An error occurred." + } + } + ], + "summary": "Retrieve a single feature from the 'A test vector layer' feature collection", + "tags": "Features" + } + }, "/wfs3collections/testlayer èé": { "get": { "description": "Metadata about a feature collection.", @@ -877,5 +1251,5 @@ Content-Type: application/openapi+json;version=3.0 "name": "Features" } ], - "timeStamp": "2019-08-19T14:43:48Z" + "timeStamp": "2019-08-27T20:35:54Z" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_empty.json b/tests/testdata/qgis_server/api/test_wfs3_collections_empty.json index a4bbcadfcf7..a5a8201cfbc 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_empty.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_empty.json @@ -19,5 +19,5 @@ Content-Type: application/json "type": "text/html" } ], - "timeStamp": "2019-07-30T09:17:49Z" + "timeStamp": "2019-08-27T20:35:54Z" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_tw_star.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_tw_star.json deleted file mode 100644 index 3f3e33de6a8..00000000000 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_tw_star.json +++ /dev/null @@ -1,40 +0,0 @@ -Content-Type: application/geo+json - -{ - "features": [ - { - "geometry": { - "coordinates": [ - 8.203547, - 44.901436 - ], - "type": "Point" - }, - "id": 1, - "properties": { - "id": 2, - "name": "two", - "utf8nameè": "two àò" - }, - "type": "Feature" - } - ], - "links": [ - { - "href": "http://server.qgis.org/wfs3/collections/testlayer3/items.geojson?name=tw*", - "rel": "self", - "title": "Retrieve the features of the collection as GEOJSON", - "type": "application/geo+json" - }, - { - "href": "http://server.qgis.org/wfs3/collections/testlayer3/items.html?name=tw*", - "rel": "alternate", - "title": "Retrieve the features of the collection as HTML", - "type": "text/html" - } - ], - "numberMatched": 1, - "numberReturned": 1, - "timeStamp": "2019-07-30T09:17:49Z", - "type": "FeatureCollection" -} \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_two.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_two.json deleted file mode 100644 index 05f280c43fe..00000000000 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer3_name_eq_two.json +++ /dev/null @@ -1,40 +0,0 @@ -Content-Type: application/geo+json - -{ - "features": [ - { - "geometry": { - "coordinates": [ - 8.203547, - 44.901436 - ], - "type": "Point" - }, - "id": 1, - "properties": { - "id": 2, - "name": "two", - "utf8nameè": "two àò" - }, - "type": "Feature" - } - ], - "links": [ - { - "href": "http://server.qgis.org/wfs3/collections/testlayer3/items.geojson?name=two", - "rel": "self", - "title": "Retrieve the features of the collection as GEOJSON", - "type": "application/geo+json" - }, - { - "href": "http://server.qgis.org/wfs3/collections/testlayer3/items.html?name=two", - "rel": "alternate", - "title": "Retrieve the features of the collection as HTML", - "type": "text/html" - } - ], - "numberMatched": 1, - "numberReturned": 1, - "timeStamp": "2019-07-30T09:17:49Z", - "type": "FeatureCollection" -} \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_tw_star.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_tw_star.json new file mode 100644 index 00000000000..ec4bdf9e8ca --- /dev/null +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_with_short_name_eq_tw_star.json @@ -0,0 +1,3 @@ +Content-Type: application/json + +[{"code":"API not found error","description":"Collection with given id (testlayer_with_short_name) was not found or multiple matches were found"}] \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé.json index d5acbcd5c03..d5e5d4543c5 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé.json @@ -67,6 +67,6 @@ Content-Type: application/geo+json ], "numberMatched": 3, "numberReturned": 3, - "timeStamp": "2019-07-30T09:17:49Z", + "timeStamp": "2019-08-27T20:35:54Z", "type": "FeatureCollection" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox.json index b827b562291..757a1e8ec87 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox.json @@ -35,6 +35,6 @@ Content-Type: application/geo+json ], "numberMatched": 1, "numberReturned": 1, - "timeStamp": "2019-07-30T09:17:49Z", + "timeStamp": "2019-08-27T20:35:54Z", "type": "FeatureCollection" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox_3857.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox_3857.json index 869a491cdab..5a425d21eb5 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox_3857.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_bbox_3857.json @@ -51,6 +51,6 @@ Content-Type: application/geo+json ], "numberMatched": 2, "numberReturned": 2, - "timeStamp": "2019-07-30T09:17:49Z", + "timeStamp": "2019-08-27T20:35:54Z", "type": "FeatureCollection" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_crs_3857.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_crs_3857.json index ce8da5830ca..67ec1e62004 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_crs_3857.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_crs_3857.json @@ -67,6 +67,6 @@ Content-Type: application/geo+json ], "numberMatched": 3, "numberReturned": 3, - "timeStamp": "2019-07-30T09:17:49Z", + "timeStamp": "2019-08-27T20:35:54Z", "type": "FeatureCollection" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1.json index ca8f3ea11c4..766b999a10a 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1.json @@ -42,6 +42,6 @@ Content-Type: application/geo+json ], "numberMatched": 3, "numberReturned": 1, - "timeStamp": "2019-07-30T09:17:49Z", + "timeStamp": "2019-08-27T20:35:54Z", "type": "FeatureCollection" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1_offset_1.json b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1_offset_1.json index d3f1499d8e2..d68a1fa08f3 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1_offset_1.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_items_testlayer_èé_limit_1_offset_1.json @@ -49,6 +49,6 @@ Content-Type: application/geo+json ], "numberMatched": 3, "numberReturned": 1, - "timeStamp": "2019-07-30T09:17:49Z", + "timeStamp": "2019-08-27T20:35:54Z", "type": "FeatureCollection" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_project.html b/tests/testdata/qgis_server/api/test_wfs3_collections_project.html index 20826b35f77..d3ce1b92ba6 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_project.html +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_project.html @@ -54,28 +54,13 @@ ">A test vector layer -

testlayer3

- - -

testlayer2

- - -

A Layer with a short name

- -

A test vector layer

A test vector layer

diff --git a/tests/testdata/qgis_server/api/test_wfs3_collections_project.json b/tests/testdata/qgis_server/api/test_wfs3_collections_project.json index ca2c98a087d..372ab71c663 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_collections_project.json +++ b/tests/testdata/qgis_server/api/test_wfs3_collections_project.json @@ -37,111 +37,6 @@ Content-Type: application/json "name": "testlayer èé", "title": "A test vector layer" }, - { - "crs": [ - "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - "http://www.opengis.net/def/crs/EPSG/9.6.2/4326", - "http://www.opengis.net/def/crs/EPSG/9.6.2/3857" - ], - "description": "", - "extent": { - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - "spatial": [ - [ - 8.203459307036344, - 44.90139483904469, - 8.203546993993488, - 44.901482526001836 - ] - ] - }, - "links": [ - { - "href": "http://server.qgis.org/wfs3/collections/testlayer3/items.json", - "rel": "item", - "title": "testlayer3 as GeoJSON", - "type": "application/geo+json" - }, - { - "href": "http://server.qgis.org/wfs3/collections/testlayer3/items.html", - "rel": "item", - "title": "testlayer3 as HTML", - "type": "text/html" - } - ], - "name": "testlayer3", - "title": "testlayer3" - }, - { - "crs": [ - "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - "http://www.opengis.net/def/crs/EPSG/9.6.2/4326", - "http://www.opengis.net/def/crs/EPSG/9.6.2/3857" - ], - "description": "", - "extent": { - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - "spatial": [ - [ - 8.203459307036344, - 44.90139483904469, - 8.203546993993488, - 44.901482526001836 - ] - ] - }, - "links": [ - { - "href": "http://server.qgis.org/wfs3/collections/testlayer2/items.json", - "rel": "item", - "title": "testlayer2 as GeoJSON", - "type": "application/geo+json" - }, - { - "href": "http://server.qgis.org/wfs3/collections/testlayer2/items.html", - "rel": "item", - "title": "testlayer2 as HTML", - "type": "text/html" - } - ], - "name": "testlayer2", - "title": "testlayer2" - }, - { - "crs": [ - "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - "http://www.opengis.net/def/crs/EPSG/9.6.2/4326", - "http://www.opengis.net/def/crs/EPSG/9.6.2/3857" - ], - "description": "A Layer with an abstract", - "extent": { - "crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", - "spatial": [ - [ - 8.203459307036344, - 44.90139483904469, - 8.203546993993488, - 44.901482526001836 - ] - ] - }, - "links": [ - { - "href": "http://server.qgis.org/wfs3/collections/layer_with_short_name/items.json", - "rel": "item", - "title": "A Layer with a short name as GeoJSON", - "type": "application/geo+json" - }, - { - "href": "http://server.qgis.org/wfs3/collections/layer_with_short_name/items.html", - "rel": "item", - "title": "A Layer with a short name as HTML", - "type": "text/html" - } - ], - "name": "layer_with_short_name", - "title": "A Layer with a short name" - }, { "crs": [ "http://www.opengis.net/def/crs/OGC/1.3/CRS84", @@ -197,19 +92,19 @@ Content-Type: application/json }, "links": [ { - "href": "http://server.qgis.org/wfs3/collections/fields_alias/items.json", + "href": "http://server.qgis.org/wfs3/collections/short-name/items.json", "rel": "item", "title": "A test vector layer as GeoJSON", "type": "application/geo+json" }, { - "href": "http://server.qgis.org/wfs3/collections/fields_alias/items.html", + "href": "http://server.qgis.org/wfs3/collections/short-name/items.html", "rel": "item", "title": "A test vector layer as HTML", "type": "text/html" } ], - "name": "fields_alias", + "name": "short-name", "title": "A test vector layer" } ], @@ -232,5 +127,5 @@ Content-Type: application/json "type": "text/html" } ], - "timeStamp": "2019-07-30T09:17:49Z" + "timeStamp": "2019-08-27T20:35:54Z" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_conformance.json b/tests/testdata/qgis_server/api/test_wfs3_conformance.json index 5f1154b01a6..c21dbf161a6 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_conformance.json +++ b/tests/testdata/qgis_server/api/test_wfs3_conformance.json @@ -22,5 +22,5 @@ Content-Type: application/json "type": "text/html" } ], - "timeStamp": "2019-07-30T09:17:49Z" + "timeStamp": "2019-08-27T20:35:54Z" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/api/test_wfs3_landing_page.json b/tests/testdata/qgis_server/api/test_wfs3_landing_page.json index 49ac5abe238..175147d436a 100644 --- a/tests/testdata/qgis_server/api/test_wfs3_landing_page.json +++ b/tests/testdata/qgis_server/api/test_wfs3_landing_page.json @@ -33,5 +33,5 @@ Content-Type: application/json "type": "application/openapi+json;version=3.0" } ], - "timeStamp": "2019-07-30T09:17:49Z" + "timeStamp": "2019-08-27T20:35:54Z" } \ No newline at end of file diff --git a/tests/testdata/qgis_server/test_project_api.qgs b/tests/testdata/qgis_server/test_project_api.qgs new file mode 100644 index 00000000000..9b461f70336 --- /dev/null +++ b/tests/testdata/qgis_server/test_project_api.qgs @@ -0,0 +1,2201 @@ + + + + QGIS Test Project API + + + + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + testlayer20150528120452665 + testlayer_c0988fd7_97ca_451d_adbc_37ad6d10583a + testlayer_0b835118_a5d5_4255_b5dd_f42253c0a4a0 + testlayer_2b89ed65_ef2f_4897_af15_9b32d4c4e040 + testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774 + testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de + landsat_a7d15b35_ca83_4b23_a9fb_af3fbdd60d15 + + + + + + + + + + + + + + + degrees + + 17.93051569743838058 + 30.1513850115596469 + 18.0579690612849646 + 30.26208897857753044 + + 0 + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 781662.375 + 3339523.125 + 793062.375 + 3350923.125 + + landsat_a7d15b35_ca83_4b23_a9fb_af3fbdd60d15 + ../landsat.tif + + + + landsat + + + +proj=utm +zone=33 +datum=WGS84 +units=m +no_defs + 3117 + 32633 + EPSG:32633 + WGS 84 / UTM zone 33N + utm + WGS84 + false + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + gdal + + + + + + + + + + + + + + + + 1 + 1 + 0 + + + + + + + + + + + + MinMax + WholeRaster + Estimated + 0.02 + 0.98 + 2 + + + 122 + 130 + StretchToMinimumMaximum + + + + + + + 0 + + + + 8.20345930703634352 + 44.90139483904469131 + 8.20354699399348775 + 44.90148252600183554 + + testlayer20150528120452665 + ./testlayer.shp + A test vector layer èé + A test vector layer with unicode òà + + + + testlayer èé + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + 0 + . + + 0 + generatedlayout + + + + + + + + + + + + name + [% 'Name: ' || "name" %] + + + + 8.20345930703634352 + 44.90139483904469131 + 8.20354699399348775 + 44.90148252600183554 + + testlayer_0b835118_a5d5_4255_b5dd_f42253c0a4a0 + ./testlayer.shp + + + + testlayer3 + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 0 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + name + + + + + 8.20345930703634352 + 44.90139483904469131 + 8.20354699399348775 + 44.90148252600183554 + + testlayer_2b89ed65_ef2f_4897_af15_9b32d4c4e040 + ./testlayer.shp + + + + testlayer2 + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + id + + + + + 8.20345930703634352 + 44.90139483904469131 + 8.20354699399348775 + 44.90148252600183554 + + testlayer_c0988fd7_97ca_451d_adbc_37ad6d10583a + ./testlayer.shp + layer1_with_short_name + A Layer1 with a short name + A Layer1 with an abstract + + + + testlayer1 + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + 0 + + + 0 + generatedlayout + + + + + + + + + + + + id + + + + + 8.20345930703634352 + 44.90139483904469131 + 8.20354699399348775 + 44.90148252600183554 + + testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de + ./testlayer.shp + A test vector layer exclude attrs + A test vector layer with unicode òà + + + + exclude_attribute + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + + + name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + 0 + . + + 0 + generatedlayout + + + + + + + + + + + + name + [% 'Name: ' || "name" %] + + + + 8.20345930703634352 + 44.90139483904469131 + 8.20354699399348775 + 44.90148252600183554 + + testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774 + ./testlayer.shp + A test vector layer with aliases + A test vector layer with aliases + + keywork one + keyword 2 + + Attribution for this layer is unknown + fields_alias + + + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + WGS84 + true + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + + + ogr + + + + + + + + + + + 1 + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + 0 + . + + 0 + generatedlayout + + + + + + + + + + + + name + [% 'Name: ' || "name" %] + + + + + + + + + + + + + + 255 + + + + + 1 + true + + + + 0 + 2 + off + + + + + + current_layer + + + 255 + 255 + 255 + 255 + 0 + 255 + 255 + + + + testlayer_0b835118_a5d5_4255_b5dd_f42253c0a4a0 + + + + false + + + + + + WGS84 + + + m2 + meters + + + 50 + 16 + 30 + true + false + false + 0 + false + false + true + 0 + 255,0,0,255 + + + false + + + true + 2 + D + + + + + + 3452 + +proj=longlat +datum=WGS84 +no_defs + EPSG:4326 + 1 + + + + + + + + + testlayer20150528120452665 + testlayer_c0988fd7_97ca_451d_adbc_37ad6d10583a + testlayer_èé_2_a5f61891_b949_43e3_ad30_84013fc922de + testlayer_èé_cf86cf11_222f_4b62_929c_12cfc82b9774 + + + 8 + 8 + 8 + 8 + + + + testlayer20150528120452665 + + + testlayer20150528120452665 + + + testlayer20150528120452665 + + + + None + true + elpaso@itopen.it + QGIS dev team + Alessandro Pasotti + + + 1 + + 8.20315414376310059 + 44.901236559338642 + 8.204164917965862 + 44.90159838674664172 + + conditions unknown + 90 + + + + 1 + + 4 + false + + + + false + Some UTF8 text èòù + true + QGIS TestProject + 0 + + false + + + + + + + + false + + + + + false + + 5000 + + + + false + + + + + + + + + + + QGIS Test Project API + + + + + + + + + + + + + 2000-01-01T00:00:00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +